Implement link groups
This commit is contained in:
parent
0723f14c53
commit
bbd25c7450
22
app/urls.py
22
app/urls.py
|
@ -25,6 +25,7 @@ from core.views import (
|
||||||
aggregators,
|
aggregators,
|
||||||
banks,
|
banks,
|
||||||
base,
|
base,
|
||||||
|
linkgroups,
|
||||||
notifications,
|
notifications,
|
||||||
platforms,
|
platforms,
|
||||||
profit,
|
profit,
|
||||||
|
@ -230,4 +231,25 @@ urlpatterns = [
|
||||||
wallets.WalletDelete.as_view(),
|
wallets.WalletDelete.as_view(),
|
||||||
name="wallet_delete",
|
name="wallet_delete",
|
||||||
),
|
),
|
||||||
|
# Link groups
|
||||||
|
path(
|
||||||
|
"links/<str:type>/",
|
||||||
|
linkgroups.LinkGroupList.as_view(),
|
||||||
|
name="linkgroups",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"links/<str:type>/create/",
|
||||||
|
linkgroups.LinkGroupCreate.as_view(),
|
||||||
|
name="linkgroup_create",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"links/<str:type>/update/<str:pk>/",
|
||||||
|
linkgroups.LinkGroupUpdate.as_view(),
|
||||||
|
name="linkgroup_update",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"links/<str:type>/delete/<str:pk>/",
|
||||||
|
linkgroups.LinkGroupDelete.as_view(),
|
||||||
|
name="linkgroup_delete",
|
||||||
|
),
|
||||||
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
106
core/forms.py
106
core/forms.py
|
@ -8,6 +8,7 @@ from .models import (
|
||||||
Ad,
|
Ad,
|
||||||
Aggregator,
|
Aggregator,
|
||||||
Asset,
|
Asset,
|
||||||
|
LinkGroup,
|
||||||
NotificationSettings,
|
NotificationSettings,
|
||||||
Platform,
|
Platform,
|
||||||
Provider,
|
Provider,
|
||||||
|
@ -73,6 +74,7 @@ class AggregatorForm(RestrictedFormMixin, ModelForm):
|
||||||
"secret_id",
|
"secret_id",
|
||||||
"secret_key",
|
"secret_key",
|
||||||
"poll_interval",
|
"poll_interval",
|
||||||
|
"link_group",
|
||||||
"enabled",
|
"enabled",
|
||||||
)
|
)
|
||||||
help_texts = {
|
help_texts = {
|
||||||
|
@ -81,6 +83,7 @@ class AggregatorForm(RestrictedFormMixin, ModelForm):
|
||||||
"secret_id": "The secret ID for the aggregator service.",
|
"secret_id": "The secret ID for the aggregator service.",
|
||||||
"secret_key": "The secret key for the aggregator service.",
|
"secret_key": "The secret key for the aggregator service.",
|
||||||
"poll_interval": "The interval in seconds to poll the aggregator service.",
|
"poll_interval": "The interval in seconds to poll the aggregator service.",
|
||||||
|
"link_group": "The link group to use for this aggregator connection.",
|
||||||
"enabled": "Whether or not the aggregator connection is enabled.",
|
"enabled": "Whether or not the aggregator connection is enabled.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +123,7 @@ class PlatformForm(RestrictedFormMixin, ModelForm):
|
||||||
"base_usd",
|
"base_usd",
|
||||||
"withdrawal_trigger",
|
"withdrawal_trigger",
|
||||||
"payees",
|
"payees",
|
||||||
|
"link_group",
|
||||||
"enabled",
|
"enabled",
|
||||||
)
|
)
|
||||||
help_texts = {
|
help_texts = {
|
||||||
|
@ -143,6 +147,7 @@ class PlatformForm(RestrictedFormMixin, ModelForm):
|
||||||
"base_usd": "The amount in USD to keep in the platform.",
|
"base_usd": "The amount in USD to keep in the platform.",
|
||||||
"withdrawal_trigger": "The amount above the base USD to trigger a withdrawal.",
|
"withdrawal_trigger": "The amount above the base USD to trigger a withdrawal.",
|
||||||
"payees": "The wallet addresses to send profit concerning this platform to.",
|
"payees": "The wallet addresses to send profit concerning this platform to.",
|
||||||
|
"link_group": "The link group to use for this platform.",
|
||||||
"enabled": "Whether or not the platform connection is enabled.",
|
"enabled": "Whether or not the platform connection is enabled.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,11 +174,12 @@ class AdForm(RestrictedFormMixin, ModelForm):
|
||||||
"dist_list",
|
"dist_list",
|
||||||
"asset_list",
|
"asset_list",
|
||||||
"provider_list",
|
"provider_list",
|
||||||
"platforms",
|
# "platforms",
|
||||||
"aggregators",
|
# "aggregators",
|
||||||
"account_whitelist",
|
"account_whitelist",
|
||||||
"send_reference",
|
"send_reference",
|
||||||
"visible",
|
"visible",
|
||||||
|
"link_group",
|
||||||
"enabled",
|
"enabled",
|
||||||
)
|
)
|
||||||
help_texts = {
|
help_texts = {
|
||||||
|
@ -185,11 +191,12 @@ class AdForm(RestrictedFormMixin, ModelForm):
|
||||||
"dist_list": "Currency and country, space separated, one pair per line.",
|
"dist_list": "Currency and country, space separated, one pair per line.",
|
||||||
"asset_list": "List of assets to distribute ads for.",
|
"asset_list": "List of assets to distribute ads for.",
|
||||||
"provider_list": "List of providers to distribute ads for.",
|
"provider_list": "List of providers to distribute ads for.",
|
||||||
"platforms": "Enabled platforms for this ad",
|
# "platforms": "Enabled platforms for this ad",
|
||||||
"aggregators": "Enabled aggregators for this ad",
|
# "aggregators": "Enabled aggregators for this ad",
|
||||||
"account_whitelist": "List of account IDs to use, one per line.",
|
"account_whitelist": "List of account IDs to use, one per line.",
|
||||||
"send_reference": "Whether or not to send the reference on new trades.",
|
"send_reference": "Whether or not to send the reference on new trades.",
|
||||||
"visible": "Whether or not this ad is visible.",
|
"visible": "Whether or not this ad is visible.",
|
||||||
|
"link_group": "The link group to use for this ad.",
|
||||||
"enabled": "Whether or not this ad is enabled.",
|
"enabled": "Whether or not this ad is enabled.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,18 +212,18 @@ class AdForm(RestrictedFormMixin, ModelForm):
|
||||||
help_text=Meta.help_texts["provider_list"],
|
help_text=Meta.help_texts["provider_list"],
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
platforms = forms.ModelMultipleChoiceField(
|
# platforms = forms.ModelMultipleChoiceField(
|
||||||
queryset=Platform.objects.all(),
|
# queryset=Platform.objects.all(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
# widget=forms.CheckboxSelectMultiple,
|
||||||
help_text=Meta.help_texts["platforms"],
|
# help_text=Meta.help_texts["platforms"],
|
||||||
required=True,
|
# required=True,
|
||||||
)
|
# )
|
||||||
aggregators = forms.ModelMultipleChoiceField(
|
# aggregators = forms.ModelMultipleChoiceField(
|
||||||
queryset=Aggregator.objects.all(),
|
# queryset=Aggregator.objects.all(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
# widget=forms.CheckboxSelectMultiple,
|
||||||
help_text=Meta.help_texts["aggregators"],
|
# help_text=Meta.help_texts["aggregators"],
|
||||||
required=True,
|
# required=True,
|
||||||
)
|
# )
|
||||||
|
|
||||||
|
|
||||||
class RequisitionForm(RestrictedFormMixin, ModelForm):
|
class RequisitionForm(RestrictedFormMixin, ModelForm):
|
||||||
|
@ -254,3 +261,70 @@ class WalletForm(RestrictedFormMixin, ModelForm):
|
||||||
"name": "The name of the wallet.",
|
"name": "The name of the wallet.",
|
||||||
"address": "The XMR address to send funds to.",
|
"address": "The XMR address to send funds to.",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LinkGroupForm(RestrictedFormMixin, ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = LinkGroup
|
||||||
|
fields = (
|
||||||
|
"name",
|
||||||
|
"aggregators",
|
||||||
|
"platforms",
|
||||||
|
"platform_owner_cut_percentage",
|
||||||
|
"requisition_owner_cut_percentage",
|
||||||
|
"operator_cut_percentage",
|
||||||
|
"enabled",
|
||||||
|
)
|
||||||
|
|
||||||
|
help_texts = {
|
||||||
|
"name": "The name of the link group.",
|
||||||
|
"aggregators": "The aggregators to use.",
|
||||||
|
"platforms": "The platforms to use.",
|
||||||
|
"platform_owner_cut_percentage": "The percentage of the total profit of this group to give to the platform owners.",
|
||||||
|
"requisition_owner_cut_percentage": "The percentage of the total profit of this group to give to the requisition owners.",
|
||||||
|
"operator_cut_percentage": "The percentage of the total profit of this group to give to the operator.",
|
||||||
|
"enabled": "Whether or not this link group is enabled.",
|
||||||
|
}
|
||||||
|
|
||||||
|
platforms = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Platform.objects.all(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
help_text=Meta.help_texts["platforms"],
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
aggregators = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=Aggregator.objects.all(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
help_text=Meta.help_texts["aggregators"],
|
||||||
|
required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super(LinkGroupForm, self).clean()
|
||||||
|
platform_owner_cut_percentage = cleaned_data.get(
|
||||||
|
"platform_owner_cut_percentage"
|
||||||
|
)
|
||||||
|
requisition_owner_cut_percentage = cleaned_data.get(
|
||||||
|
"requisition_owner_cut_percentage"
|
||||||
|
)
|
||||||
|
operator_cut_percentage = cleaned_data.get("operator_cut_percentage")
|
||||||
|
total_sum = (
|
||||||
|
platform_owner_cut_percentage
|
||||||
|
+ requisition_owner_cut_percentage
|
||||||
|
+ operator_cut_percentage
|
||||||
|
)
|
||||||
|
if total_sum != 100:
|
||||||
|
self.add_error(
|
||||||
|
"platform_owner_cut_percentage",
|
||||||
|
f"The sum of the percentages must be 100, not {total_sum}.",
|
||||||
|
)
|
||||||
|
self.add_error(
|
||||||
|
"requisition_owner_cut_percentage",
|
||||||
|
f"The sum of the percentages must be 100, not {total_sum}.",
|
||||||
|
)
|
||||||
|
self.add_error(
|
||||||
|
"operator_cut_percentage",
|
||||||
|
f"The sum of the percentages must be 100, not {total_sum}.",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
return cleaned_data
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Generated by Django 4.1.7 on 2023-03-18 10:12
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0029_alter_requisition_id_alter_wallet_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='LinkGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('platform_owner_cut_percentage', models.FloatField(default=0)),
|
||||||
|
('requisition_owner_cut_percentage', models.FloatField(default=0)),
|
||||||
|
('operator_cut_percentage', models.FloatField(default=0)),
|
||||||
|
('enabled', models.BooleanField(default=True)),
|
||||||
|
('aggregators', models.ManyToManyField(to='core.aggregator')),
|
||||||
|
('platforms', models.ManyToManyField(to='core.platform')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Generated by Django 4.1.7 on 2023-03-18 10:38
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0030_linkgroup'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ad',
|
||||||
|
name='aggregators',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='ad',
|
||||||
|
name='platforms',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='linkgroup',
|
||||||
|
name='aggregators',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='linkgroup',
|
||||||
|
name='platforms',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='ad',
|
||||||
|
name='link_group',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.linkgroup'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='aggregator',
|
||||||
|
name='link_group',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.linkgroup'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='platform',
|
||||||
|
name='link_group',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.linkgroup'),
|
||||||
|
),
|
||||||
|
]
|
143
core/models.py
143
core/models.py
|
@ -50,6 +50,26 @@ class NotificationSettings(models.Model):
|
||||||
return f"Notification settings for {self.user}"
|
return f"Notification settings for {self.user}"
|
||||||
|
|
||||||
|
|
||||||
|
class LinkGroup(models.Model):
|
||||||
|
"""
|
||||||
|
A group linking Aggregators, Platforms and defining a percentage split
|
||||||
|
that the owners of each should receive.
|
||||||
|
"""
|
||||||
|
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
platform_owner_cut_percentage = models.FloatField(default=0)
|
||||||
|
requisition_owner_cut_percentage = models.FloatField(default=0)
|
||||||
|
operator_cut_percentage = models.FloatField(default=0)
|
||||||
|
|
||||||
|
enabled = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Aggregator(models.Model):
|
class Aggregator(models.Model):
|
||||||
"""
|
"""
|
||||||
A connection to an API aggregator to pull transactions from bank accounts.
|
A connection to an API aggregator to pull transactions from bank accounts.
|
||||||
|
@ -70,6 +90,10 @@ class Aggregator(models.Model):
|
||||||
|
|
||||||
fetch_accounts = models.BooleanField(default=True)
|
fetch_accounts = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
link_group = models.ForeignKey(
|
||||||
|
LinkGroup, on_delete=models.CASCADE, null=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -85,36 +109,41 @@ class Aggregator(models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_for_platform(cls, platform):
|
def get_for_platform(cls, platform):
|
||||||
aggregators = []
|
# aggregators = []
|
||||||
ads = Ad.objects.filter(
|
# linkgroups = LinkGroup.objects.filter(
|
||||||
platforms=platform,
|
# platforms=platform,
|
||||||
enabled=True,
|
# enabled=True,
|
||||||
)
|
# )
|
||||||
for ad in ads:
|
# for link in linkgroups:
|
||||||
for aggregator in ad.aggregators.all():
|
# for aggregator in link.aggregators.all():
|
||||||
if aggregator not in aggregators:
|
# if aggregator not in aggregators:
|
||||||
aggregators.append(aggregator)
|
# aggregators.append(aggregator)
|
||||||
|
platform_link = platform.link_group
|
||||||
|
|
||||||
return aggregators
|
# return aggregators
|
||||||
|
return cls.objects.filter(
|
||||||
|
link_group=platform_link,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def platforms(self):
|
def platforms(self):
|
||||||
"""
|
"""
|
||||||
Get platforms for this aggregator.
|
Get platforms for this aggregator.
|
||||||
Do this by looking up Ads with the aggregator.
|
Do this by looking up LinkGroups with the aggregator.
|
||||||
Then, join them all together.
|
Then, join them all together.
|
||||||
"""
|
"""
|
||||||
platforms = []
|
return Platform.objects.filter(link_group=self.link_group)
|
||||||
ads = Ad.objects.filter(
|
# platforms = []
|
||||||
aggregators=self,
|
# linkgroups = LinkGroup.objects.filter(
|
||||||
enabled=True,
|
# aggregators=self,
|
||||||
)
|
# enabled=True,
|
||||||
for ad in ads:
|
# )
|
||||||
for platform in ad.platforms.all():
|
# for link in linkgroups:
|
||||||
if platform not in platforms:
|
# for platform in link.platforms.all():
|
||||||
platforms.append(platform)
|
# if platform not in platforms:
|
||||||
|
# platforms.append(platform)
|
||||||
|
|
||||||
return platforms
|
# return platforms
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_currencies_for_platform(cls, platform):
|
def get_currencies_for_platform(cls, platform):
|
||||||
|
@ -168,6 +197,9 @@ class Wallet(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
address = models.CharField(max_length=255)
|
address = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Platform(models.Model):
|
class Platform(models.Model):
|
||||||
"""
|
"""
|
||||||
|
@ -205,6 +237,10 @@ class Platform(models.Model):
|
||||||
|
|
||||||
payees = models.ManyToManyField(Wallet, blank=True)
|
payees = models.ManyToManyField(Wallet, blank=True)
|
||||||
|
|
||||||
|
link_group = models.ForeignKey(
|
||||||
|
LinkGroup, on_delete=models.CASCADE, null=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -214,7 +250,9 @@ class Platform(models.Model):
|
||||||
ad_id = self.platform_ad_ids.get(platform_ad_id, None)
|
ad_id = self.platform_ad_ids.get(platform_ad_id, None)
|
||||||
if not ad_id:
|
if not ad_id:
|
||||||
return None
|
return None
|
||||||
ad_object = Ad.objects.filter(id=ad_id, user=self.user, enabled=True).first()
|
ad_object = Ad.objects.filter(
|
||||||
|
id=ad_id, user=self.user, link_group=self.link_group, enabled=True
|
||||||
|
).first()
|
||||||
return ad_object
|
return ad_object
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -234,7 +272,9 @@ class Platform(models.Model):
|
||||||
"""
|
"""
|
||||||
Get all ads linked to this platform.
|
Get all ads linked to this platform.
|
||||||
"""
|
"""
|
||||||
return Ad.objects.filter(user=self.user, enabled=True, platforms=self)
|
return Ad.objects.filter(
|
||||||
|
user=self.user, enabled=True, link_group=self.link_group
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ads_assets(self):
|
def ads_assets(self):
|
||||||
|
@ -343,36 +383,45 @@ class Platform(models.Model):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_for_aggregator(cls, aggregator):
|
def get_for_aggregator(cls, aggregator):
|
||||||
platforms = []
|
# platforms = []
|
||||||
ads = Ad.objects.filter(
|
# linkgroups = LinkGroup.objects.filter(
|
||||||
aggregators=aggregator,
|
# aggregators=aggregator,
|
||||||
enabled=True,
|
# enabled=True,
|
||||||
)
|
# )
|
||||||
for ad in ads:
|
# for link in linkgroups:
|
||||||
for platform in ad.platforms.all():
|
# for platform in link.platforms.all():
|
||||||
if platform not in platforms:
|
# if platform not in platforms:
|
||||||
platforms.append(platform)
|
# platforms.append(platform)
|
||||||
|
|
||||||
return platforms
|
# return platforms
|
||||||
|
|
||||||
|
aggregator_link = aggregator.link_group
|
||||||
|
return cls.objects.filter(
|
||||||
|
link_group=aggregator_link,
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def aggregators(self):
|
def aggregators(self):
|
||||||
"""
|
"""
|
||||||
Get aggregators for this platform.
|
Get aggregators for this platform.
|
||||||
Do this by looking up Ads with the platform.
|
Do this by looking up LinkGroups with the platform.
|
||||||
Then, join them all together.
|
Then, join them all together.
|
||||||
"""
|
"""
|
||||||
aggregators = []
|
# aggregators = []
|
||||||
ads = Ad.objects.filter(
|
# linkgroups = LinkGroup.objects.filter(
|
||||||
platforms=self,
|
# platforms=self,
|
||||||
enabled=True,
|
# enabled=True,
|
||||||
)
|
# )
|
||||||
for ad in ads:
|
# for link in linkgroups:
|
||||||
for aggregator in ad.aggregators.all():
|
# for aggregator in link.aggregators.all():
|
||||||
if aggregator not in aggregators:
|
# if aggregator not in aggregators:
|
||||||
aggregators.append(aggregator)
|
# aggregators.append(aggregator)
|
||||||
|
|
||||||
return aggregators
|
# return aggregators
|
||||||
|
|
||||||
|
return Aggregator.objects.filter(
|
||||||
|
link_group=self.link_group,
|
||||||
|
)
|
||||||
|
|
||||||
def get_requisition(self, aggregator_id, requisition_id):
|
def get_requisition(self, aggregator_id, requisition_id):
|
||||||
"""
|
"""
|
||||||
|
@ -425,15 +474,15 @@ class Ad(models.Model):
|
||||||
asset_list = models.ManyToManyField(Asset)
|
asset_list = models.ManyToManyField(Asset)
|
||||||
provider_list = models.ManyToManyField(Provider)
|
provider_list = models.ManyToManyField(Provider)
|
||||||
|
|
||||||
platforms = models.ManyToManyField(Platform)
|
|
||||||
aggregators = models.ManyToManyField(Aggregator)
|
|
||||||
|
|
||||||
account_map = models.JSONField(default=dict)
|
account_map = models.JSONField(default=dict)
|
||||||
account_whitelist = models.TextField(null=True, blank=True)
|
account_whitelist = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
send_reference = models.BooleanField(default=True)
|
send_reference = models.BooleanField(default=True)
|
||||||
|
|
||||||
visible = models.BooleanField(default=True)
|
visible = models.BooleanField(default=True)
|
||||||
|
link_group = models.ForeignKey(
|
||||||
|
LinkGroup, on_delete=models.CASCADE, null=True, blank=True
|
||||||
|
)
|
||||||
enabled = models.BooleanField(default=True)
|
enabled = models.BooleanField(default=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -272,7 +272,10 @@
|
||||||
Platform Connections
|
Platform Connections
|
||||||
</a>
|
</a>
|
||||||
<a class="navbar-item" href="{% url 'wallets' type='page' %}">
|
<a class="navbar-item" href="{% url 'wallets' type='page' %}">
|
||||||
Wallets
|
Profit Wallets
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="{% url 'linkgroups' type='page' %}">
|
||||||
|
Link Groups
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -28,6 +28,19 @@
|
||||||
<td>{{ item.accounts|length }}</td>
|
<td>{{ item.accounts|length }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-get="{% url 'requisition_update' type=type aggregator_id=pk req_id=item.id %}"
|
||||||
|
hx-trigger="click"
|
||||||
|
hx-target="#modals-here"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
class="button">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon has-text-black" data-tooltip="Configure">
|
||||||
|
<i class="fa-solid fa-wrench"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
hx-delete="{% url 'req_delete' type=type pk=pk req_id=item.id %}"
|
hx-delete="{% url 'req_delete' type=type pk=pk req_id=item.id %}"
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
{% load cache %}
|
||||||
|
{% load cachalot cache %}
|
||||||
|
{% get_last_invalidation 'core.LinkGroup' as last %}
|
||||||
|
{% include 'mixins/partials/notify.html' %}
|
||||||
|
{# cache 600 objects_linkgroups request.user.id object_list type last #}
|
||||||
|
<table
|
||||||
|
class="table is-fullwidth is-hoverable"
|
||||||
|
hx-target="#{{ context_object_name }}-table"
|
||||||
|
id="{{ context_object_name }}-table"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-trigger="{{ context_object_name_singular }}Event from:body"
|
||||||
|
hx-get="{{ list_url }}">
|
||||||
|
<thead>
|
||||||
|
<th>id</th>
|
||||||
|
<th>user</th>
|
||||||
|
<th>name</th>
|
||||||
|
<th>platform</th>
|
||||||
|
<th>requisition</th>
|
||||||
|
<th>operator</th>
|
||||||
|
<th>enabled</th>
|
||||||
|
<th>actions</th>
|
||||||
|
</thead>
|
||||||
|
{% for item in object_list %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
class="has-text-grey"
|
||||||
|
onclick="window.prompt('Copy to clipboard: Ctrl+C, Enter', '{{ item.id }}');">
|
||||||
|
<span class="icon" data-tooltip="Copy to clipboard">
|
||||||
|
<i class="fa-solid fa-copy" aria-hidden="true"></i>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.user }}</td>
|
||||||
|
<td>{{ item.name }}</td>
|
||||||
|
<td>{{ item.platform_owner_cut_percentage }}</td>
|
||||||
|
<td>{{ item.requisition_owner_cut_percentage }}</td>
|
||||||
|
<td>{{ item.operator_cut_percentage }}</td>
|
||||||
|
<td>
|
||||||
|
{% if item.enabled %}
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa-solid fa-check"></i>
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa-solid fa-xmark"></i>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="buttons">
|
||||||
|
<button
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-get="{% url 'linkgroup_update' type=type pk=item.id %}"
|
||||||
|
hx-trigger="click"
|
||||||
|
hx-target="#{{ type }}s-here"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
class="button">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa-solid fa-pencil"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-delete="{% url 'linkgroup_delete' type=type pk=item.id %}"
|
||||||
|
hx-trigger="click"
|
||||||
|
hx-target="#modals-here"
|
||||||
|
hx-swap="innerHTML"
|
||||||
|
hx-confirm="Are you sure you wish to delete {{ item.name }}?"
|
||||||
|
class="button">
|
||||||
|
<span class="icon-text">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fa-solid fa-xmark"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</table>
|
||||||
|
{# endcache #}
|
|
@ -1,6 +1,6 @@
|
||||||
{% load cache %}
|
{% load cache %}
|
||||||
{% load cachalot cache %}
|
{% load cachalot cache %}
|
||||||
{% get_last_invalidation 'core.Platform' as last %}
|
{% get_last_invalidation 'core.Trade' as last %}
|
||||||
{% include 'mixins/partials/notify.html' %}
|
{% include 'mixins/partials/notify.html' %}
|
||||||
{# cache 600 objects_platform_trades request.user.id object_list type last #}
|
{# cache 600 objects_platform_trades request.user.id object_list type last #}
|
||||||
{% for platform_name, trade_map in object_list.items %}
|
{% for platform_name, trade_map in object_list.items %}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% load cache %}
|
{% load cache %}
|
||||||
{% load cachalot cache %}
|
{% load cachalot cache %}
|
||||||
{% get_last_invalidation 'core.Ad' as last %}
|
{% get_last_invalidation 'core.Wallet' as last %}
|
||||||
{% include 'mixins/partials/notify.html' %}
|
{% include 'mixins/partials/notify.html' %}
|
||||||
{# cache 600 objects_ads request.user.id object_list type last #}
|
{# cache 600 objects_wallets request.user.id object_list type last #}
|
||||||
<table
|
<table
|
||||||
class="table is-fullwidth is-hoverable"
|
class="table is-fullwidth is-hoverable"
|
||||||
hx-target="#{{ context_object_name }}-table"
|
hx-target="#{{ context_object_name }}-table"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.test import TransactionTestCase
|
from django.test import TransactionTestCase
|
||||||
|
|
||||||
from core.clients.platform import LocalPlatformClient
|
from core.clients.platform import LocalPlatformClient
|
||||||
from core.models import Ad, Asset, Provider, Requisition
|
from core.models import Ad, Asset, LinkGroup, Provider, Requisition
|
||||||
from core.tests.helpers import AggregatorPlatformMixin
|
from core.tests.helpers import AggregatorPlatformMixin
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,11 +93,22 @@ class TestPlatform(AggregatorPlatformMixin, TransactionTestCase):
|
||||||
|
|
||||||
self.ad.asset_list.set([asset])
|
self.ad.asset_list.set([asset])
|
||||||
self.ad.provider_list.set([provider])
|
self.ad.provider_list.set([provider])
|
||||||
self.ad.platforms.set([self.platform])
|
# self.ad.platforms.set([self.platform])
|
||||||
self.ad.aggregators.set([self.aggregator])
|
# self.ad.aggregators.set([self.aggregator])
|
||||||
|
|
||||||
self.ad.save()
|
self.ad.save()
|
||||||
|
|
||||||
|
self.linkgroup = LinkGroup.objects.create(
|
||||||
|
user=self.user,
|
||||||
|
name="Test",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.aggregator.link_group = self.linkgroup
|
||||||
|
self.aggregator.save()
|
||||||
|
|
||||||
|
self.platform.link_group = self.linkgroup
|
||||||
|
self.platform.save()
|
||||||
|
|
||||||
self.req = Requisition.objects.create(
|
self.req = Requisition.objects.create(
|
||||||
user=self.user,
|
user=self.user,
|
||||||
aggregator=self.aggregator,
|
aggregator=self.aggregator,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from mixins.views import ObjectCreate, ObjectDelete, ObjectList, ObjectUpdate
|
||||||
|
from two_factor.views.mixins import OTPRequiredMixin
|
||||||
|
|
||||||
|
from core.forms import LinkGroupForm
|
||||||
|
from core.models import LinkGroup
|
||||||
|
|
||||||
|
|
||||||
|
class LinkGroupList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
||||||
|
list_template = "partials/linkgroup-list.html"
|
||||||
|
model = LinkGroup
|
||||||
|
page_title = "List of link groups"
|
||||||
|
page_subtitle = "Link groups are used to link aggregators and platforms"
|
||||||
|
|
||||||
|
list_url_name = "linkgroups"
|
||||||
|
list_url_args = ["type"]
|
||||||
|
|
||||||
|
submit_url_name = "linkgroup_create"
|
||||||
|
|
||||||
|
|
||||||
|
class LinkGroupCreate(LoginRequiredMixin, OTPRequiredMixin, ObjectCreate):
|
||||||
|
model = LinkGroup
|
||||||
|
form_class = LinkGroupForm
|
||||||
|
|
||||||
|
submit_url_name = "linkgroup_create"
|
||||||
|
|
||||||
|
|
||||||
|
class LinkGroupUpdate(LoginRequiredMixin, OTPRequiredMixin, ObjectUpdate):
|
||||||
|
model = LinkGroup
|
||||||
|
form_class = LinkGroupForm
|
||||||
|
|
||||||
|
submit_url_name = "linkgroup_update"
|
||||||
|
|
||||||
|
|
||||||
|
class LinkGroupDelete(LoginRequiredMixin, OTPRequiredMixin, ObjectDelete):
|
||||||
|
model = LinkGroup
|
|
@ -9,7 +9,7 @@ from core.models import Wallet
|
||||||
class WalletList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
class WalletList(LoginRequiredMixin, OTPRequiredMixin, ObjectList):
|
||||||
list_template = "partials/wallet-list.html"
|
list_template = "partials/wallet-list.html"
|
||||||
model = Wallet
|
model = Wallet
|
||||||
page_title = "List of wallets"
|
page_title = "List of wallets to send profit to"
|
||||||
|
|
||||||
list_url_name = "wallets"
|
list_url_name = "wallets"
|
||||||
list_url_args = ["type"]
|
list_url_args = ["type"]
|
||||||
|
|
Loading…
Reference in New Issue