diff --git a/app/urls.py b/app/urls.py index 5357efa..b81555e 100644 --- a/app/urls.py +++ b/app/urls.py @@ -239,23 +239,44 @@ urlpatterns = [ ), # Asset Groups path( - "assetgroup//", + "group//", assets.AssetGroupList.as_view(), name="assetgroups", ), path( - "assetgroup//create/", + "group//create/", assets.AssetGroupCreate.as_view(), name="assetgroup_create", ), path( - "assetgroup//update//", + "group//update//", assets.AssetGroupUpdate.as_view(), name="assetgroup_update", ), path( - "assetgroup//delete//", + "group//delete//", assets.AssetGroupDelete.as_view(), name="assetgroup_delete", ), + # Asset Restrictions + path( + "restriction///", + assets.AssetRestrictionList.as_view(), + name="assetrestrictions", + ), + path( + "restriction//create//", + assets.AssetRestrictionCreate.as_view(), + name="assetrestriction_create", + ), + path( + "restriction//update///", + assets.AssetRestrictionUpdate.as_view(), + name="assetrestriction_update", + ), + path( + "restriction//delete///", + assets.AssetRestrictionDelete.as_view(), + name="assetrestriction_delete", + ), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/core/forms.py b/core/forms.py index 6a7da8e..3da6f2f 100644 --- a/core/forms.py +++ b/core/forms.py @@ -6,6 +6,7 @@ from django.forms import ModelForm from .models import ( Account, AssetGroup, + AssetRestriction, Hook, NotificationSettings, RiskModel, @@ -342,3 +343,37 @@ class AssetGroupForm(RestrictedFormMixin, ModelForm): "description": "Description of the asset group. Informational only.", "account": "Account to pull assets from.", } + + +class AssetRestrictionForm(RestrictedFormMixin, ModelForm): + class Meta: + model = AssetRestriction + fields = ( + "name", + "description", + "pairs", + ) + 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.", + } + + 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 + + return cleaned_data diff --git a/core/migrations/0053_assetrestriction_pairs_parsed_and_more.py b/core/migrations/0053_assetrestriction_pairs_parsed_and_more.py new file mode 100644 index 0000000..498bdf0 --- /dev/null +++ b/core/migrations/0053_assetrestriction_pairs_parsed_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.1.4 on 2023-02-10 13:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0052_assetgroup_assetrestriction'), + ] + + operations = [ + migrations.AddField( + model_name='assetrestriction', + name='pairs_parsed', + field=models.JSONField(blank=True, null=True), + ), + migrations.AlterField( + model_name='assetgroup', + name='allowed', + field=models.JSONField(blank=True, default={}, null=True), + ), + ] diff --git a/core/models.py b/core/models.py index 8545fa4..3434fc3 100644 --- a/core/models.py +++ b/core/models.py @@ -410,7 +410,7 @@ class AssetGroup(models.Model): account = models.ForeignKey(Account, on_delete=models.CASCADE) # Dict like {"RUB": True, "USD": False} - allowed = models.JSONField(null=True, blank=True, default={}) + allowed = models.JSONField(null=True, blank=True, default=dict) def __str__(self): return self.name @@ -424,12 +424,20 @@ class AssetGroup(models.Model): truthy_values = [x for x in self.allowed.values() if x is True] 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) group = models.ForeignKey( AssetGroup, on_delete=models.CASCADE, null=True, blank=True diff --git a/core/templates/partials/assetgroup-list.html b/core/templates/partials/assetgroup-list.html index aa1a486..98e387c 100644 --- a/core/templates/partials/assetgroup-list.html +++ b/core/templates/partials/assetgroup-list.html @@ -14,6 +14,7 @@ description account status + restrictions actions {% for item in object_list %} @@ -24,6 +25,7 @@ {{ item.description }} {{ item.account }} {{ item.matches }} + {{ item.restrictions }}
- {% if type == 'page' %} - - - {% else %} - - {% endif %} + + +
diff --git a/core/templates/partials/assetrestriction-list.html b/core/templates/partials/assetrestriction-list.html new file mode 100644 index 0000000..3300e04 --- /dev/null +++ b/core/templates/partials/assetrestriction-list.html @@ -0,0 +1,61 @@ +{% include 'partials/notify.html' %} + + + + + + + + + + + + {% for item in object_list %} + + + + + + + + + + {% endfor %} + +
idusernamedescriptionpairsgroupactions
{{ item.id }}{{ item.user }}{{ item.name }}{{ item.description }}{{ item.pairs_parsed|length }}{{ item.group }} +
+ + +
+
diff --git a/core/views/assets.py b/core/views/assets.py index 2a4ff52..2a6f9e5 100644 --- a/core/views/assets.py +++ b/core/views/assets.py @@ -1,12 +1,14 @@ from django.contrib.auth.mixins import LoginRequiredMixin -from core.forms import AssetGroupForm -from core.models import AssetGroup +from core.forms import AssetGroupForm, AssetRestrictionForm +from core.models import AssetGroup, AssetRestriction from core.util import logs -from core.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate +from core.views import AbortSave, ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate log = logs.get_logger(__name__) +# Asset Groups + class AssetGroupList(LoginRequiredMixin, ObjectList): list_template = "partials/assetgroup-list.html" @@ -35,3 +37,65 @@ class AssetGroupUpdate(LoginRequiredMixin, ObjectUpdate): class AssetGroupDelete(LoginRequiredMixin, ObjectDelete): model = AssetGroup + + +# Asset Restrictions + + +class AssetRestrictionsPermissionMixin: + # Check the user has permission to view the asset group + # We have a user check on the AssetRestriction, but we need to check the + # AssetGroup as well + def set_extra_args(self, user): + self.extra_permission_args = { + "group__user": user, + "group__pk": self.kwargs["group"], + } + + +class AssetRestrictionList( + LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectList +): + list_template = "partials/assetrestriction-list.html" + model = AssetRestriction + page_title = "List of asset restrictions. Linked to asset groups." + + list_url_name = "assetrestrictions" + list_url_args = ["type", "group"] + + submit_url_name = "assetrestriction_create" + submit_url_args = ["type", "group"] + + +class AssetRestrictionCreate( + LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectCreate +): + model = AssetRestriction + form_class = AssetRestrictionForm + + submit_url_name = "assetrestriction_create" + submit_url_args = ["type", "group"] + + def pre_save_mutate(self, user, obj): + try: + assetgroup = AssetGroup.objects.get(pk=self.kwargs["group"], user=user) + obj.group = assetgroup + except AssetGroup.DoesNotExist: + log.error(f"Asset Group {self.kwargs['group']} does not exist") + raise AbortSave("asset group does not exist or you don't have access") + + +class AssetRestrictionUpdate( + LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectUpdate +): + model = AssetRestriction + form_class = AssetRestrictionForm + + submit_url_name = "assetrestriction_update" + submit_url_args = ["type", "pk", "group"] + + +class AssetRestrictionDelete( + LoginRequiredMixin, AssetRestrictionsPermissionMixin, ObjectDelete +): + model = AssetRestriction