Remove asset restrictions and make asset groups smarter

This commit is contained in:
Mark Veidemanis 2023-02-13 07:20:40 +00:00
parent 287facbab2
commit dcfb963be6
Signed by: m
GPG Key ID: 5ACFCEED46C0904F
10 changed files with 383 additions and 172 deletions

View File

@ -76,7 +76,7 @@ urlpatterns = [
), ),
path( path(
f"{settings.ASSET_PATH}/<str:webhook_id>/", f"{settings.ASSET_PATH}/<str:webhook_id>/",
assets.AssetRestrictionAPI.as_view(), assets.AssetGroupAPI.as_view(),
name="asset", name="asset",
), ),
path("signals/<str:type>/", signals.SignalList.as_view(), name="signals"), path("signals/<str:type>/", signals.SignalList.as_view(), name="signals"),
@ -264,26 +264,26 @@ urlpatterns = [
name="assetgroup_delete", name="assetgroup_delete",
), ),
# Asset Restrictions # Asset Restrictions
path( # path(
"restriction/<str:type>/<str:group>/", # "restriction/<str:type>/<str:group>/",
assets.AssetRestrictionList.as_view(), # assets.AssetRestrictionList.as_view(),
name="assetrestrictions", # name="assetrestrictions",
), # ),
path( # path(
"restriction/<str:type>/create/<str:group>/", # "restriction/<str:type>/create/<str:group>/",
assets.AssetRestrictionCreate.as_view(), # assets.AssetRestrictionCreate.as_view(),
name="assetrestriction_create", # name="assetrestriction_create",
), # ),
path( # path(
"restriction/<str:type>/update/<str:group>/<str:pk>/", # "restriction/<str:type>/update/<str:group>/<str:pk>/",
assets.AssetRestrictionUpdate.as_view(), # assets.AssetRestrictionUpdate.as_view(),
name="assetrestriction_update", # name="assetrestriction_update",
), # ),
path( # path(
"restriction/<str:type>/delete/<str:group>/<str:pk>/", # "restriction/<str:type>/delete/<str:group>/<str:pk>/",
assets.AssetRestrictionDelete.as_view(), # assets.AssetRestrictionDelete.as_view(),
name="assetrestriction_delete", # name="assetrestriction_delete",
), # ),
# Asset group filters # Asset group filters
path( path(
"assetfilter/<str:group_id>/flip/<str:symbol>/", "assetfilter/<str:group_id>/flip/<str:symbol>/",

View File

@ -2,10 +2,9 @@ from django.contrib import admin
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from .forms import CustomUserCreationForm from .forms import CustomUserCreationForm
from .models import ( from .models import ( # AssetRestriction,
Account, Account,
AssetGroup, AssetGroup,
AssetRestriction,
Callback, Callback,
Hook, Hook,
NotificationSettings, NotificationSettings,
@ -94,11 +93,11 @@ class RiskModelAdmin(admin.ModelAdmin):
class AssetGroupAdmin(admin.ModelAdmin): class AssetGroupAdmin(admin.ModelAdmin):
list_display = ("user", "name", "description") list_display = ("user", "name", "description", "webhook_id")
class AssetRestrictionAdmin(admin.ModelAdmin): # class AssetRestrictionAdmin(admin.ModelAdmin):
list_display = ("user", "name", "description", "webhook_id", "group") # list_display = ("user", "name", "description", "webhook_id", "group")
admin.site.register(User, CustomUserAdmin) admin.site.register(User, CustomUserAdmin)
@ -115,4 +114,4 @@ admin.site.register(Strategy, StrategyAdmin)
admin.site.register(NotificationSettings, NotificationSettingsAdmin) admin.site.register(NotificationSettings, NotificationSettingsAdmin)
admin.site.register(RiskModel, RiskModelAdmin) admin.site.register(RiskModel, RiskModelAdmin)
admin.site.register(AssetGroup, AssetGroupAdmin) admin.site.register(AssetGroup, AssetGroupAdmin)
admin.site.register(AssetRestriction, AssetRestrictionAdmin) # admin.site.register(AssetRestriction, AssetRestrictionAdmin)

View File

@ -4,10 +4,9 @@ from django.core.exceptions import FieldDoesNotExist
from django.forms import ModelForm from django.forms import ModelForm
from mixins.restrictions import RestrictedFormMixin from mixins.restrictions import RestrictedFormMixin
from .models import ( from .models import ( # AssetRestriction,
Account, Account,
AssetGroup, AssetGroup,
AssetRestriction,
Hook, Hook,
NotificationSettings, NotificationSettings,
RiskModel, RiskModel,
@ -309,47 +308,85 @@ class AssetGroupForm(RestrictedFormMixin, ModelForm):
fields = ( fields = (
"name", "name",
"description", "description",
"aggregation",
"trigger_below",
"trigger_above",
) )
help_texts = { help_texts = {
"name": "Name of the asset group. Informational only.", "name": "Name of the asset group. Informational only.",
"description": "Description of the asset group. Informational only.", "description": "Description of the asset group. Informational only.",
"aggregation": "The aggregation method to use for this asset group.",
"trigger_below": "Trigger when the aggregation is below this value.",
"trigger_above": "Trigger when the aggregation is above this value.",
} }
class AssetRestrictionForm(RestrictedFormMixin, ModelForm):
class Meta:
model = AssetRestriction
fields = (
"name",
"description",
"pairs",
"pairs_parsed",
)
help_texts = {
"name": "Name of the asset restriction group. Informational only.",
"description": "Description of the asset restriction group. Informational only.",
"pairs": "Comma-separated list of pairs to restrict when a webhook is received. This does nothing on its own.",
}
pairs_parsed = forms.BooleanField(widget=forms.HiddenInput, required=False)
def clean(self): def clean(self):
cleaned_data = super(AssetRestrictionForm, self).clean() cleaned_data = super(AssetGroupForm, self).clean()
if "pairs" in cleaned_data and cleaned_data["pairs"]: if "aggregation" in cleaned_data:
new_pairs = [] if cleaned_data["aggregation"] == "none":
pair_split = cleaned_data["pairs"].split(",") if "trigger_below" in cleaned_data and cleaned_data["trigger_below"]:
if not pair_split: self.add_error(
self.add_error("pairs", "You must specify at least one pair.") "trigger_below",
"You cannot specify a trigger below value when aggregation is set to none.",
)
return return
for pair in pair_split: if "trigger_above" in cleaned_data and cleaned_data["trigger_above"]:
if pair: self.add_error(
new_pairs.append(pair.strip()) "trigger_above",
else: "You cannot specify a trigger above value when aggregation is set to none.",
self.add_error("pairs", f"You cannot have an empty pair: {pair}") )
return return
cleaned_data["pairs_parsed"] = new_pairs
else: else:
cleaned_data["pairs_parsed"] = {} # Check if either trigger_below or trigger_above has been set
if not any(
[cleaned_data["trigger_below"], cleaned_data["trigger_above"]]
):
self.add_error(
"trigger_below",
"You must specify a trigger below and/or trigger above value when aggregation is set to anything other than none.",
)
self.add_error(
"trigger_above",
"You must specify a trigger below and/or trigger above value when aggregation is set to anything other than none.",
)
return
return cleaned_data return cleaned_data
# class AssetRestrictionForm(RestrictedFormMixin, ModelForm):
# class Meta:
# model = AssetRestriction
# fields = (
# "name",
# "description",
# "pairs",
# "pairs_parsed",
# )
# help_texts = {
# "name": "Name of the asset restriction group. Informational only.",
# "description": "Description of the asset restriction group. Informational only.",
# "pairs": "Comma-separated list of pairs to restrict when a webhook is received. This does nothing on its own.",
# }
# pairs_parsed = forms.BooleanField(widget=forms.HiddenInput, required=False)
# def clean(self):
# cleaned_data = super(AssetRestrictionForm, self).clean()
# if "pairs" in cleaned_data and cleaned_data["pairs"]:
# new_pairs = []
# pair_split = cleaned_data["pairs"].split(",")
# if not pair_split:
# self.add_error("pairs", "You must specify at least one pair.")
# return
# for pair in pair_split:
# if pair:
# new_pairs.append(pair.strip())
# else:
# self.add_error("pairs", f"You cannot have an empty pair: {pair}")
# return
# cleaned_data["pairs_parsed"] = new_pairs
# else:
# cleaned_data["pairs_parsed"] = {}
# return cleaned_data

View File

@ -0,0 +1,23 @@
# Generated by Django 4.1.6 on 2023-02-13 10:28
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0058_remove_assetgroup_account'),
]
operations = [
migrations.AddField(
model_name='assetgroup',
name='webhook_id',
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
),
migrations.DeleteModel(
name='AssetRestriction',
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 4.1.6 on 2023-02-13 10:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0059_assetgroup_webhook_id_delete_assetrestriction'),
]
operations = [
migrations.AddField(
model_name='assetgroup',
name='aggregation',
field=models.CharField(choices=[('none', 'None'), ('avg_sentiment', 'Average sentiment')], default='none', max_length=255),
),
migrations.AddField(
model_name='assetgroup',
name='trigger_above',
field=models.FloatField(blank=True, null=True),
),
migrations.AddField(
model_name='assetgroup',
name='trigger_below',
field=models.FloatField(blank=True, null=True),
),
]

View File

@ -41,6 +41,10 @@ SIGNAL_TYPE_CHOICES = (
("exit", "Exit"), ("exit", "Exit"),
("trend", "Trend"), ("trend", "Trend"),
) )
AGGREGATION_CHOICES = (
("none", "None"),
("avg_sentiment", "Average sentiment"),
)
class Plan(models.Model): class Plan(models.Model):
@ -414,8 +418,17 @@ class AssetGroup(models.Model):
# Dict like {"RUB": True, "USD": False} # Dict like {"RUB": True, "USD": False}
allowed = models.JSONField(null=True, blank=True, default=dict) allowed = models.JSONField(null=True, blank=True, default=dict)
webhook_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
aggregation = models.CharField(
choices=AGGREGATION_CHOICES, max_length=255, default="none"
)
trigger_below = models.FloatField(null=True, blank=True)
trigger_above = models.FloatField(null=True, blank=True)
def __str__(self): def __str__(self):
return f"{self.name} ({self.restrictions})" return f"{self.name}"
@property @property
def matches(self): def matches(self):
@ -426,23 +439,16 @@ class AssetGroup(models.Model):
truthy_values = [x for x in self.allowed.values() if x is True] truthy_values = [x for x in self.allowed.values() if x is True]
return f"{len(truthy_values)}/{len(self.allowed)}" return f"{len(truthy_values)}/{len(self.allowed)}"
@property
def restrictions(self):
"""
Get the total number of restrictions for this group.
"""
return self.assetrestriction_set.count()
# class AssetRestriction(models.Model):
# user = models.ForeignKey(User, on_delete=models.CASCADE)
# name = models.CharField(max_length=255)
# description = models.TextField(null=True, blank=True)
# pairs = models.CharField(max_length=4096, null=True, blank=True)
# pairs_parsed = models.JSONField(null=True, blank=True, default=list)
class AssetRestriction(models.Model): # webhook_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
pairs = models.CharField(max_length=4096, null=True, blank=True)
pairs_parsed = models.JSONField(null=True, blank=True, default=list)
webhook_id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True) # group = models.ForeignKey(
# AssetGroup, on_delete=models.CASCADE, null=True, blank=True
group = models.ForeignKey( # )
AssetGroup, on_delete=models.CASCADE, null=True, blank=True
)

View File

@ -1,6 +1,6 @@
{% load cache %} {% load cache %}
{% load cachalot cache %} {% load cachalot cache %}
{% get_last_invalidation 'core.AssetGroup' 'core.AssetRestriction' as last %} {% get_last_invalidation 'core.AssetGroup' as last %}
{% include 'mixins/partials/notify.html' %} {% include 'mixins/partials/notify.html' %}
{% cache 600 objects_assetgroups request.user.id object_list last %} {% cache 600 objects_assetgroups request.user.id object_list last %}
<table <table
@ -16,7 +16,7 @@
<th>name</th> <th>name</th>
<th>description</th> <th>description</th>
<th>status</th> <th>status</th>
<th>restrictions</th> <th>hook</th>
<th>actions</th> <th>actions</th>
</thead> </thead>
{% for item in object_list %} {% for item in object_list %}
@ -35,7 +35,15 @@
{{ item.matches }} {{ item.matches }}
</a> </a>
</td> </td>
<td>{{ item.restrictions }}</td> <td>
<a
class="has-text-grey"
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{settings.URL}}/{{settings.ASSET_PATH}}/{{ item.webhook_id }}/');">
<span class="icon" data-tooltip="Copy to clipboard">
<i class="fa-solid fa-copy" aria-hidden="true"></i>
</span>
</a>
</td>
<td> <td>
<div class="buttons"> <div class="buttons">
<button <button
@ -65,15 +73,6 @@
</span> </span>
</span> </span>
</button> </button>
<a href="{% url 'assetrestrictions' type='page' group=item.id %}"><button
class="button">
<span class="icon-text">
<span class="icon">
<i class="fa-solid fa-eye"></i>
</span>
</span>
</button>
</a>
</div> </div>
</td> </td>
</tr> </tr>

View File

@ -1,7 +1,7 @@
from django.test import TestCase from django.test import TestCase
from core.models import AssetGroup, User from core.models import AssetGroup, User
from core.trading.assetfilter import get_allowed from core.trading import assetfilter
class AssetfilterTestCase(TestCase): class AssetfilterTestCase(TestCase):
@ -21,8 +21,75 @@ class AssetfilterTestCase(TestCase):
Test that the asset filter works. Test that the asset filter works.
""" """
self.group.allowed = {"EUR_USD": True, "EUR_GBP": False} self.group.allowed = {"EUR_USD": True, "EUR_GBP": False}
self.assertTrue(get_allowed(self.group, "EUR_USD", "buy")) self.assertTrue(assetfilter.get_allowed(self.group, "EUR_USD", "buy"))
self.assertFalse(get_allowed(self.group, "EUR_GBP", "sell")) self.assertFalse(assetfilter.get_allowed(self.group, "EUR_GBP", "sell"))
# Default true # Default true
self.assertTrue(get_allowed(self.group, "nonexistent", "sell")) self.assertTrue(assetfilter.get_allowed(self.group, "nonexistent", "sell"))
def test_check_asset_aggregation(self):
"""
Test that the asset aggregation works.
"""
# Test within lower bound
self.assertTrue(
assetfilter.check_asset_aggregation(
1.0, trigger_above=None, trigger_below=2.0
)
)
# Test within upper bound
self.assertTrue(
assetfilter.check_asset_aggregation(
1.0, trigger_above=0.0, trigger_below=None
)
)
# Test within bounds
self.assertTrue(
assetfilter.check_asset_aggregation(
1.0, trigger_above=0.0, trigger_below=2.0
)
)
# Test outside bounds
self.assertFalse(
assetfilter.check_asset_aggregation(
1.0, trigger_above=2.0, trigger_below=3.0
)
)
# Test outside lower bound
self.assertFalse(
assetfilter.check_asset_aggregation(
1.0, trigger_above=None, trigger_below=0.0
)
)
# Test outside upper bound
self.assertFalse(
assetfilter.check_asset_aggregation(
1.0, trigger_above=2.0, trigger_below=None
)
)
# Test no bounds, just to be sure
self.assertFalse(
assetfilter.check_asset_aggregation(
1.0, trigger_above=None, trigger_below=None
)
)
# Test both bounds, but inverted
self.assertFalse(
assetfilter.check_asset_aggregation(
1.0, trigger_above=2.0, trigger_below=0.0
)
)
# Test within negative and positive bounds
self.assertTrue(
assetfilter.check_asset_aggregation(
-1.0, trigger_above=-2.0, trigger_below=0.0
)
)

View File

@ -12,3 +12,22 @@ def get_allowed(group, symbol, direction):
return True return True
return allowed[symbol] return allowed[symbol]
def check_asset_aggregation(value, trigger_above, trigger_below):
"""
Check if the value is within the bounds of the aggregation
"""
# If both are defined
if trigger_above is not None and trigger_below is not None:
if value > trigger_above and value < trigger_below:
return True
return False
if trigger_below is not None:
if value < trigger_below:
# Value is less than lower bound, match
return True
if trigger_above is not None:
if value > trigger_above:
return True
return False

View File

@ -1,12 +1,10 @@
import json import json
from cachalot.api import invalidate
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import render from django.shortcuts import render
from django.views import View from django.views import View
from mixins.views import ( from mixins.views import (
AbortSave,
ObjectCreate, ObjectCreate,
ObjectDelete, ObjectDelete,
ObjectList, ObjectList,
@ -17,8 +15,9 @@ from rest_framework import status
from rest_framework.parsers import JSONParser from rest_framework.parsers import JSONParser
from rest_framework.views import APIView from rest_framework.views import APIView
from core.forms import AssetGroupForm, AssetRestrictionForm from core.forms import AssetGroupForm # , AssetRestrictionForm
from core.models import AssetGroup, AssetRestriction from core.models import AssetGroup # , AssetRestriction
from core.trading import assetfilter
from core.util import logs from core.util import logs
log = logs.get_logger(__name__) log = logs.get_logger(__name__)
@ -59,73 +58,73 @@ class AssetGroupDelete(LoginRequiredMixin, ObjectDelete):
# Asset Restrictions # Asset Restrictions
class AssetRestrictionsPermissionMixin: # class AssetRestrictionsPermissionMixin:
# Check the user has permission to view the asset group # # Check the user has permission to view the asset group
# We have a user check on the AssetRestriction, but we need to check the # # We have a user check on the AssetRestriction, but we need to check the
# AssetGroup as well # # AssetGroup as well
def set_extra_args(self, user): # def set_extra_args(self, user):
self.extra_permission_args = { # self.extra_permission_args = {
"group__user": user, # "group__user": user,
"group__pk": self.kwargs["group"], # "group__pk": self.kwargs["group"],
} # }
class AssetRestrictionList( # class AssetRestrictionList(
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectList # LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectList
): # ):
list_template = "partials/assetrestriction-list.html" # list_template = "partials/assetrestriction-list.html"
model = AssetRestriction # model = AssetRestriction
page_title = "List of asset restrictions. Linked to asset groups." # page_title = "List of asset restrictions. Linked to asset groups."
page_subtitle = ( # page_subtitle = (
"Allows API calls to permit or prohibit trading on defined currency pairs." # "Allows API calls to permit or prohibit trading on defined currency pairs."
) # )
list_url_name = "assetrestrictions" # list_url_name = "assetrestrictions"
list_url_args = ["type", "group"] # list_url_args = ["type", "group"]
submit_url_name = "assetrestriction_create" # submit_url_name = "assetrestriction_create"
submit_url_args = ["type", "group"] # submit_url_args = ["type", "group"]
class AssetRestrictionCreate( # class AssetRestrictionCreate(
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectCreate # LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectCreate
): # ):
model = AssetRestriction # model = AssetRestriction
form_class = AssetRestrictionForm # form_class = AssetRestrictionForm
submit_url_name = "assetrestriction_create" # submit_url_name = "assetrestriction_create"
submit_url_args = ["type", "group"] # submit_url_args = ["type", "group"]
def form_invalid(self, form): # def form_invalid(self, form):
"""If the form is invalid, render the invalid form.""" # """If the form is invalid, render the invalid form."""
return self.get(self.request, **self.kwargs, form=form) # return self.get(self.request, **self.kwargs, form=form)
def pre_save_mutate(self, user, obj): # def pre_save_mutate(self, user, obj):
try: # try:
assetgroup = AssetGroup.objects.get(pk=self.kwargs["group"], user=user) # assetgroup = AssetGroup.objects.get(pk=self.kwargs["group"], user=user)
obj.group = assetgroup # obj.group = assetgroup
except AssetGroup.DoesNotExist: # except AssetGroup.DoesNotExist:
log.error(f"Asset Group {self.kwargs['group']} does not exist") # log.error(f"Asset Group {self.kwargs['group']} does not exist")
raise AbortSave("asset group does not exist or you don't have access") # raise AbortSave("asset group does not exist or you don't have access")
class AssetRestrictionUpdate( # class AssetRestrictionUpdate(
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectUpdate # LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectUpdate
): # ):
model = AssetRestriction # model = AssetRestriction
form_class = AssetRestrictionForm # form_class = AssetRestrictionForm
submit_url_name = "assetrestriction_update" # submit_url_name = "assetrestriction_update"
submit_url_args = ["type", "pk", "group"] # submit_url_args = ["type", "pk", "group"]
class AssetRestrictionDelete( # class AssetRestrictionDelete(
LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectDelete # LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectDelete
): # ):
model = AssetRestriction # model = AssetRestriction
class AssetRestrictionAPI(APIView): class AssetGroupAPI(APIView):
parser_classes = [JSONParser] parser_classes = [JSONParser]
def post(self, request, webhook_id): def post(self, request, webhook_id):
@ -133,16 +132,16 @@ class AssetRestrictionAPI(APIView):
print(json.dumps(request.data, indent=2)) print(json.dumps(request.data, indent=2))
try: try:
restriction = AssetRestriction.objects.get(webhook_id=webhook_id) group = AssetGroup.objects.get(webhook_id=webhook_id)
except AssetRestriction.DoesNotExist: except AssetGroup.DoesNotExist:
log.error(f"Asset restriction {webhook_id} does not exist") log.error(f"Asset group {webhook_id} does not exist")
return HttpResponse(status=status.HTTP_404_NOT_FOUND) return HttpResponse(status=status.HTTP_404_NOT_FOUND)
if restriction.group is not None: # if restriction.group is not None:
group = restriction.group # group = restriction.group
else: # else:
log.error(f"Asset restriction {restriction} has no group") # log.error(f"Asset restriction {restriction} has no group")
return HttpResponse(status=status.HTTP_404_NOT_FOUND) # return HttpResponse(status=status.HTTP_404_NOT_FOUND)
# if group.strategy_set.exists() is not None: # if group.strategy_set.exists() is not None:
# strategies = group.strategy_set.all() # strategies = group.strategy_set.all()
@ -151,18 +150,52 @@ class AssetRestrictionAPI(APIView):
# return HttpResponse(status=status.HTTP_404_NOT_FOUND) # return HttpResponse(status=status.HTTP_404_NOT_FOUND)
# log.debug(f"Asset API {webhook_id} matched to strategies {strategies}") # log.debug(f"Asset API {webhook_id} matched to strategies {strategies}")
if "meta" in request.data: if "meta" not in request.data:
if "is_match" in request.data["meta"]: log.error(f"Asset API {webhook_id} has no meta")
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
if "is_match" not in request.data["meta"]:
log.error(f"Asset API {webhook_id} has no is_match")
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
is_match = request.data["meta"]["is_match"] is_match = request.data["meta"]["is_match"]
if isinstance(restriction.pairs_parsed, list): if "topic" not in request.data:
for pair in restriction.pairs_parsed: log.error(f"Asset API {webhook_id} has no topic")
group.allowed[pair] = is_match return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
topic = request.data["topic"]
print("YES TOPIC", topic)
new_pairs = []
pair_split = topic.split(",")
if not pair_split:
log.error(f"Asset API {webhook_id} topic {topic} is not a pair")
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
for pair in pair_split:
if pair:
new_pairs.append(pair.strip())
else:
log.error(f"Asset API {webhook_id} pair {pair} is not a pair")
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
print("YES PAIRS", new_pairs)
# Check if we have lower/upper bounds
if group.aggregation != "none":
if "aggs" in request.data["meta"]:
aggs = request.data["meta"]["aggs"]
if group.aggregation in aggs:
if "value" in aggs[group.aggregation]:
value = aggs[group.aggregation]["value"]
print("YES AVG", value)
is_match = assetfilter.check_asset_aggregation(
value, group.trigger_above, group.trigger_below
)
print("YES AVG IS MATCH", is_match)
for pair in new_pairs:
group.allowed[pair] = is_match is True or None
group.save() group.save()
invalidate(restriction)
invalidate(group)
return HttpResponse(status=status.HTTP_200_OK) return HttpResponse(status=status.HTTP_200_OK)
return HttpResponse(status=status.HTTP_400_BAD_REQUEST)
# Asset group allowed field # Asset group allowed field