diff --git a/app/urls.py b/app/urls.py index c3895e5..23265fb 100644 --- a/app/urls.py +++ b/app/urls.py @@ -20,7 +20,7 @@ from django.contrib.auth.views import LogoutView from django.urls import include, path from two_factor.urls import urlpatterns as tf_urls -from core.views import aggregators, banks, base, notifications, platforms +from core.views import ads, aggregators, banks, base, notifications, platforms # from core.views.stripe_callbacks import Callback @@ -149,4 +149,35 @@ urlpatterns = [ platforms.PlatformTrades.as_view(), name="trades", ), + # Ads + path( + "ads//", + ads.AdList.as_view(), + name="ads", + ), + path( + "ads//create/", + ads.AdCreate.as_view(), + name="ad_create", + ), + path( + "ads//update//", + ads.AdUpdate.as_view(), + name="ad_update", + ), + path( + "ads//delete//", + ads.AdDelete.as_view(), + name="ad_delete", + ), + path( + "ops/ads/dist/", + ads.AdDist.as_view(), + name="ad_dist", + ), + path( + "ops/ads/redist/", + ads.AdRedist.as_view(), + name="ad_redist", + ), ] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) diff --git a/core/clients/platform.py b/core/clients/platform.py index a3319d7..3b942dc 100644 --- a/core/clients/platform.py +++ b/core/clients/platform.py @@ -74,7 +74,6 @@ class LocalPlatformClient(ABC): # dash = await self.api.dashboard() dash = await self.call("dashboard") - print("DASH22", dash) # if dash["response"] is None: # return False dash_tmp = {} @@ -110,9 +109,9 @@ class LocalPlatformClient(ABC): # reference = db.tx_to_ref(contact_id) # buyer = contact["data"]["buyer"]["username"] # amount = contact["data"]["amount"] - # if self.platform == "agora": + # if self.name == "agora": # asset = contact["data"]["advertisement"]["asset"] - # elif self.platform == "lbtc": + # elif self.name == "lbtc": # asset = "BTC" # if asset == "XMR": # amount_crypto = contact["data"]["amount_xmr"] @@ -124,7 +123,7 @@ class LocalPlatformClient(ABC): # continue # rtrn.append( # ( - # f"[#] [{reference}] ({self.platform}) <{buyer}>" + # f"[#] [{reference}] ({self.name}) <{buyer}>" # f" {amount}{currency} {provider} {amount_crypto}{asset}" # ) # ) @@ -146,9 +145,9 @@ class LocalPlatformClient(ABC): current_trades.append(reference) buyer = contact["data"]["buyer"]["username"] amount = contact["data"]["amount"] - if self.platform == "agora": + if self.name == "agora": asset = contact["data"]["advertisement"]["asset"] - elif self.platform == "lbtc": + elif self.name == "lbtc": asset = "BTC" provider = contact["data"]["advertisement"]["payment_method"] if asset == "XMR": @@ -160,7 +159,7 @@ class LocalPlatformClient(ABC): continue if reference not in self.last_dash: reference = await self.new_trade( - self.platform, + self.name, asset, contact_id, buyer, @@ -175,7 +174,7 @@ class LocalPlatformClient(ABC): # Let us know there is a new trade title = "New trade" message = ( - f"[#] [{reference}] ({self.platform}) <{buyer}>" + f"[#] [{reference}] ({self.name}) <{buyer}>" f" {amount}{currency} {provider} {amount_crypto}{asset}" ) await notify.sendmsg(self.instance.user, message, title=title) @@ -188,7 +187,7 @@ class LocalPlatformClient(ABC): self.last_dash.remove(ref) if reference and reference not in current_trades: current_trades.append(reference) - messages = await db.cleanup(self.platform, current_trades) + messages = await db.cleanup(self.name, current_trades) for message in messages: await notify.sendmsg(self.instance.user, message, title="Cleanup") @@ -225,7 +224,7 @@ class LocalPlatformClient(ABC): if reference in self.last_messages: if not [user, message] in self.last_messages[reference]: self.irc.sendmsg( - f"[{reference}] ({self.platform}) <{user}> {message}" + f"[{reference}] ({self.name}) <{user}> {message}" ) # Append sent messages to last_messages so we don't send # them again @@ -234,7 +233,7 @@ class LocalPlatformClient(ABC): self.last_messages[reference] = [[user, message]] for x in messages_tmp[reference]: self.irc.sendmsg( - f"[{reference}] ({self.platform}) <{user}> {message}" + f"[{reference}] ({self.name}) <{user}> {message}" ) # Purge old trades from cache @@ -245,7 +244,7 @@ class LocalPlatformClient(ABC): return messages_tmp async def enum_ad_ids(self, page=0): - if self.platform == "lbtc" and page == 0: + if self.name == "lbtc" and page == 0: page = 1 ads = await self.api.ads(page=page) # ads = await self.api._api_call(api_method="ads", query_values={"page": page}) @@ -269,7 +268,7 @@ class LocalPlatformClient(ABC): return ads_total async def enum_ads(self, requested_asset=None, page=0): - if self.platform == "lbtc" and page == 0: + if self.name == "lbtc" and page == 0: page = 1 query_values = {"page": page} if requested_asset: @@ -284,9 +283,9 @@ class LocalPlatformClient(ABC): if not ads["response"]: return False for ad in ads["response"]["data"]["ad_list"]: - if self.platform == "agora": + if self.name == "agora": asset = ad["data"]["asset"] - elif self.platform == "lbtc": + elif self.name == "lbtc": asset = "BTC" ad_id = ad["data"]["ad_id"] country = ad["data"]["countrycode"] @@ -330,7 +329,7 @@ class LocalPlatformClient(ABC): return sec_ago_date < 172800 async def enum_public_ads(self, asset, currency, providers=None, page=0): - if self.platform == "lbtc" and page == 0: + if self.name == "lbtc" and page == 0: page = 1 to_return = [] # if asset == "XMR": @@ -363,7 +362,7 @@ class LocalPlatformClient(ABC): return False for ad in ads["response"]["data"]["ad_list"]: provider = ad["data"]["online_provider"] - if self.platform == "lbtc": + if self.name == "lbtc": provider_test = self.map_provider(provider) else: provider_test = provider @@ -423,7 +422,7 @@ class LocalPlatformClient(ABC): return False # Get the ads to update - to_update = self.get_new_ad_equations(self.platform, public_ads, assets) + to_update = self.get_new_ad_equations(self.name, public_ads, assets) await self.slow_ad_update(to_update) async def get_all_public_ads(self, assets=None, currencies=None, providers=None): @@ -438,18 +437,11 @@ class LocalPlatformClient(ABC): "BTC": "bitcoin", } - if not assets: - assets = self.get_all_assets(self.platform) + assets = assets or self.instance.ads_assets # Get all currencies we have ads for, deduplicated - if not currencies: - currencies = self.get_all_currencies(self.platform) - if not providers: - providers = self.get_all_providers(self.platform) - sinks_currencies = self.currencies - supported_currencies = [ - currency for currency in currencies if currency in sinks_currencies - ] - currencies = supported_currencies + + providers = providers or self.instance.ads_providers + currencies = currencies or self.instance.currencies # We want to get the ads for each of these currencies and return the result rates = await money.cg.get_price( ids=["monero", "bitcoin"], vs_currencies=currencies @@ -466,7 +458,7 @@ class LocalPlatformClient(ABC): if not ads_list: log.debug("Error getting ads list.") continue - ads = await money.lookup_rates(self.platform, ads_list, rates=rates) + ads = await money.lookup_rates(self.name, ads_list, rates=rates) if not ads: log.debug("Error lookup up rates.") continue @@ -493,7 +485,7 @@ class LocalPlatformClient(ABC): "margin": ad[6], "ts": str(datetime.now().isoformat()), "xtype": msgtype, - "market": self.platform, + "market": self.name, "type": "platform", "user_id": self.instance.user.id, "platform_id": self.instance.id, @@ -568,6 +560,7 @@ class LocalPlatformClient(ABC): currency, provider, payment_details, + ad, visible=None, edit=False, ad_id=None, @@ -590,19 +583,26 @@ class LocalPlatformClient(ABC): if payment_details: payment_details_text = self.format_payment_details( - currency, payment_details + currency, + payment_details, + ad, ) - ad_text = self.format_ad(asset, currency, payment_details_text) - min_amount, max_amount = money.get_minmax(self.platform, asset, currency) - if self.platform == "lbtc": + ad_text = self.format_ad(asset, currency, payment_details_text, ad) + min_amount, max_amount = await money.get_minmax( + self.instance.min_trade_size_usd, + self.instance.max_trade_size_usd, + asset, + currency, + ) + if self.name == "lbtc": bank_name = payment_details["bank"] - # if self.platform == "agora": + # if self.name == "agora": price_formula = ( f"coingecko{asset.lower()}usd*" f"usd{currency.lower()}*{self.instance.margin}" ) - # elif self.platform == "lbtc": + # elif self.name == "lbtc": # price_formula = f"btc_in_usd*{self.instance.margin}*USD_in_{currency}" form = { @@ -616,13 +616,12 @@ class LocalPlatformClient(ABC): "online_provider": provider, "require_feedback_score": 0, } - if self.platform == "agora": + if self.name == "agora": form["asset"] = asset - form["payment_method_details"] = "Revolut" + form["payment_method_details"] = ad.payment_method_details form["online_provider"] = provider - elif self.platform == "lbtc": + elif self.name == "lbtc": form["online_provider"] = self.map_provider(provider, reverse=True) - if visible is False: form["visible"] = False elif visible is True: @@ -632,27 +631,28 @@ class LocalPlatformClient(ABC): form["msg"] = ad_text form["min_amount"] = round(min_amount, 2) form["max_amount"] = round(max_amount, 2) - if self.platform == "lbtc": + if self.name == "lbtc": form["bank_name"] = bank_name + if edit: ad = await self.api.ad(ad_id=ad_id, **form) else: ad = await self.api.ad_create(**form) return ad - async def dist_countries(self, filter_asset=None): + async def dist_countries(self, ad, filter_asset=None): """ Distribute our advert into all countries and providers listed in the config. Exits on errors. :return: False or dict with response :rtype: bool or dict """ - dist_list = list(self.create_distribution_list(self.platform, filter_asset)) + dist_list = list(self.create_distribution_list(self.name, ad, filter_asset)) our_ads = await self.enum_ads() ( supported_currencies, account_info, - ) = self.get_valid_account_details(self.platform) + ) = self.get_valid_account_details(self.name) # Let's get rid of the ad IDs and make it a tuple like dist_list our_ads = [(x[0], x[2], x[3], x[4]) for x in our_ads] if not our_ads: @@ -669,6 +669,8 @@ class LocalPlatformClient(ABC): currency, provider, payment_details=account_info[currency], + ad=ad, + visible=ad.visible, ) # Bail on first error, let's not continue if rtrn is False: @@ -676,7 +678,7 @@ class LocalPlatformClient(ABC): to_return.append(rtrn) return to_return - async def redist_countries(self): + async def redist_countries(self, ad): """ Redistribute our advert details into all our listed adverts. This will edit all ads and update the details. Only works if we have already @@ -690,12 +692,16 @@ class LocalPlatformClient(ABC): ( supported_currencies, account_info, - ) = self.get_valid_account_details(self.platform) + ) = self.get_valid_account_details(self.name) if not our_ads: log.error("Could not get our ads.") return False to_return = [] for asset, ad_id, countrycode, currency, provider in our_ads: + if asset not in self.instance.ads_assets: + continue + if provider not in self.instance.ads_providers: + continue if currency in supported_currencies: rtrn = await self.create_ad( asset, @@ -703,6 +709,8 @@ class LocalPlatformClient(ABC): currency, provider, payment_details=account_info[currency], + ad=ad, + visible=ad.visible, edit=True, ad_id=ad_id, ) @@ -907,7 +915,7 @@ class LocalPlatformClient(ABC): f"When sending the payment please use reference code: {reference}", ) - async def send_bank_details(self, platform, currency, trade_id): + async def send_bank_details(self, platform, currency, trade_id, ad): """ Send the bank details to a trade. """ @@ -916,7 +924,7 @@ class LocalPlatformClient(ABC): if send_setting == "1": account_info = self.get_matching_account_details(platform, currency) formatted_account_info = self.format_payment_details( - currency, account_info, real=True + currency, account_info, ad, real=True ) if not formatted_account_info: log.error(f"Payment info invalid: {formatted_account_info}") @@ -927,10 +935,10 @@ class LocalPlatformClient(ABC): ) def get_all_assets(self, platform): - return ["XMR"] # TODO + raise Exception def get_all_providers(self, platform): - return ["REVOLUT"] # TODO + raise Exception def get_all_currencies(self, platform): raise Exception @@ -957,10 +965,9 @@ class LocalPlatformClient(ABC): # call slow update. # (asset, currency, provider) - if not assets: - assets = self.get_all_assets(platform) + assets = self.instance.ads_assets currencies = self.currencies - providers = self.get_all_providers(platform) + providers = self.instance.ads_providers # if platform == "lbtc": # providers = [ # self.sources.lbtc.map_provider(x, reverse=True) for x in providers @@ -1092,48 +1099,25 @@ class LocalPlatformClient(ABC): # log.debug("Cheapest ad above our min that is not us: {x}", x=cheapest_ad) return cheapest_ad_margin - def create_distribution_list(self, platform, filter_asset=None): + def create_distribution_list(self, platform, ad, filter_asset=None): """ Create a list for distribution of ads. :return: generator of asset, countrycode, currency, provider :rtype: generator of tuples """ - distlist = [ - ["AUD", "AU"], - ["BGN", "BG"], - ["CAD", "CA"], - ["CHF", "CH"], - ["CZK", "CZ"], - ["DKK", "DK"], - ["GBP", "GB"], - ["USD", "GB"], - ["EUR", "GB"], - ["USD", "US"], - ["GBP", "US"], - ["EUR", "US"], - ["HKD", "HK"], - ["HRK", "HR"], - ["HUF", "HU"], - ["ISK", "IS"], - ["JPY", "JP"], - ["MXN", "MX"], - ["NOK", "NO"], - ["NZD", "NZ"], - ["PLN", "PL"], - ["RON", "RO"], - ["RUB", "RU"], - ["SEK", "SE"], - ["EUR", "SE"], - ["SGD", "SG"], - ["THB", "TH"], - ["TRY", "TR"], - ["ZAR", "ZA"], - ] + dist_list_raw = ad.dist_list + dist_list_lines = dist_list_raw.splitlines() + + distlist = [] + for line in dist_list_lines: + l_currency, l_country = line.split() + distlist.append([l_currency, l_country]) + # Iterate providers like REVOLUT, NATIONAL_BANK - for provider in self.get_all_providers(platform): + for provider in self.instance.ads_providers: # Iterate assets like XMR, BTC - for asset in self.get_all_assets(platform): + for asset in self.instance.ads_assets: # Iterate pairs of currency and country like EUR, GB for currency, countrycode in distlist: if filter_asset: @@ -1144,7 +1128,7 @@ class LocalPlatformClient(ABC): def get_valid_account_details(self, platform): currencies = self.instance.currencies - account_info = self.sinks.account_info + account_info = self.instance.account_info currency_account_info_map = {} for currency in currencies: for bank, accounts in account_info.items(): @@ -1153,7 +1137,7 @@ class LocalPlatformClient(ABC): currency_account_info_map[currency] = account["account_number"] currency_account_info_map[currency]["bank"] = bank.split("_")[0] currency_account_info_map[currency]["recipient"] = account[ - "recipient" + "ownerName" ] return (currencies, currency_account_info_map) @@ -1166,104 +1150,104 @@ class LocalPlatformClient(ABC): return False return currency_account_info_map[currency] - def _distribute_account_details(self, platform, currencies=None, account_info=None): - """ - Distribute account details for ads. - We will disable ads we can't support. - """ - - if not currencies: - currencies = self.instance.currencies - if not account_info: - account_info = self.instance.account_info - ( - supported_currencies, - currency_account_info_map, - ) = self.get_valid_account_details(platform) - - # not_supported = [currency for currency in all_currencies if - # currency not in supported_currencies] - - our_ads = self.enum_ads() - - supported_ads = [ad for ad in our_ads if ad[3] in supported_currencies] - - not_supported_ads = [ad for ad in our_ads if ad[3] not in supported_currencies] + # def _distribute_account_details(self, platform, currencies=None, account_info=None): + # """ + # Distribute account details for ads. + # We will disable ads we can't support. + # """ - for ad in supported_ads: - asset = ad[0] - countrycode = ad[2] - currency = ad[3] - provider = ad[4] - payment_details = currency_account_info_map[currency] - ad_id = ad[1] - self.create_ad( - asset, - countrycode, - currency, - provider, - payment_details, - visible=True, - edit=True, - ad_id=ad_id, - ) + # if not currencies: + # currencies = self.instance.currencies + # if not account_info: + # account_info = self.instance.account_info + # ( + # supported_currencies, + # currency_account_info_map, + # ) = self.get_valid_account_details(platform) + + # # not_supported = [currency for currency in all_currencies if + # # currency not in supported_currencies] + + # our_ads = self.enum_ads() + + # supported_ads = [ad for ad in our_ads if ad[3] in supported_currencies] + + # not_supported_ads = [ad for ad in our_ads if ad[3] not in supported_currencies] + + # for ad in supported_ads: + # asset = ad[0] + # countrycode = ad[2] + # currency = ad[3] + # provider = ad[4] + # payment_details = currency_account_info_map[currency] + # ad_id = ad[1] + # self.create_ad( + # asset, + # countrycode, + # currency, + # provider, + # payment_details, + # visible=True, + # edit=True, + # ad_id=ad_id, + # ) - for ad in not_supported_ads: - asset = ad[0] - countrycode = ad[2] - currency = ad[3] - provider = ad[4] - ad_id = ad[1] - self.create_ad( - asset, - countrycode, - currency, - provider, - payment_details=False, - visible=False, - edit=True, - ad_id=ad_id, - ) + # for ad in not_supported_ads: + # asset = ad[0] + # countrycode = ad[2] + # currency = ad[3] + # provider = ad[4] + # ad_id = ad[1] + # self.create_ad( + # asset, + # countrycode, + # currency, + # provider, + # payment_details=False, + # visible=False, + # edit=True, + # ad_id=ad_id, + # ) - def distribute_account_details(self, currencies=None, account_info=None): - """ - Helper to distribute the account details for all platforms. - """ - platforms = "agora" - for platform in platforms: - self._distribute_account_details( - platform, currencies=currencies, account_info=account_info - ) + # def distribute_account_details(self, currencies=None, account_info=None): + # """ + # Helper to distribute the account details for all platforms. + # """ + # platforms = "agora" + # for platform in platforms: + # self._distribute_account_details( + # platform, currencies=currencies, account_info=account_info + # ) - def format_ad(self, asset, currency, payment_details_text): + def format_ad(self, asset, currency, payment_details_text, ad): """ Format the ad. """ - ad = settings.Platform.Ad # TODO + ad_text = ad.text # Substitute the currency - ad = ad.replace("$CURRENCY$", currency) + ad_text = ad_text.replace("$CURRENCY$", currency) # Substitute the asset - ad = ad.replace("$ASSET$", asset) + ad_text = ad_text.replace("$ASSET$", asset) # Substitute the payment details - ad = ad.replace("$PAYMENT$", payment_details_text) + ad_text = ad_text.replace("$PAYMENT$", payment_details_text) # Strip extra tabs - ad = ad.replace("\\t", "\t") - return ad + ad_text = ad_text.replace("\\t", "\t") + return ad_text - def format_payment_details(self, currency, payment_details, real=False): + def format_payment_details(self, currency, payment_details, ad, real=False): """ Format the payment details. """ if not payment_details: return False if real: - payment = settings.Platform.PaymentDetailsReal + payment = ad.payment_details_real else: - payment = settings.Platform.PaymentDetails + payment = ad.payment_details payment_text = "" for field, value in payment_details.items(): diff --git a/core/clients/platforms/api/agoradesk.py b/core/clients/platforms/api/agoradesk.py index f7e4e21..e992787 100644 --- a/core/clients/platforms/api/agoradesk.py +++ b/core/clients/platforms/api/agoradesk.py @@ -1,12 +1,12 @@ """See https://agoradesk.com/api-docs/v1.""" # pylint: disable=too-many-lines # Large API. Lots of lines can't be avoided. -import json import logging from typing import Any, Dict, List, Optional, Union import aiohttp import arrow +import orjson from core.util import logs @@ -71,7 +71,7 @@ class AgoraDesk: logger.debug("Headers : %s", headers) logger.debug("HTTP Method : %s", http_method) logger.debug("Query Values: %s", query_values) - logger.debug("Query Values as Json:\n%s", json.dumps(query_values)) + logger.debug("Query Values as Json:\n%s", orjson.dumps(query_values)) result: Dict[str, Any] = { "success": False, @@ -83,7 +83,7 @@ class AgoraDesk: response = None if http_method == "POST": if query_values: - cast["data"] = query_values + cast["data"] = orjson.dumps(query_values) async with aiohttp.ClientSession() as session: async with session.post(api_call_url, **cast) as response_raw: response = await response_raw.json() @@ -98,7 +98,6 @@ class AgoraDesk: response = await response_raw.json() status_code = response_raw.status if response: - print("YES RESPONSE", response) logger.debug(response) result["status"] = status_code if status_code == 200: @@ -501,7 +500,7 @@ class AgoraDesk: params["lat"] = lat if lon: params["lon"] = lon - if visible: + if visible is not None: params["visible"] = True if visible else False return await self._api_call( diff --git a/core/forms.py b/core/forms.py index 1f44684..1ca7f3b 100644 --- a/core/forms.py +++ b/core/forms.py @@ -4,7 +4,15 @@ from django.core.exceptions import FieldDoesNotExist from django.forms import ModelForm from mixins.restrictions import RestrictedFormMixin -from .models import Aggregator, NotificationSettings, Platform, User +from .models import ( + Ad, + Aggregator, + Asset, + NotificationSettings, + Platform, + Provider, + User, +) # flake8: noqa: E501 @@ -128,3 +136,64 @@ class PlatformForm(RestrictedFormMixin, ModelForm): "no_reference_amount_check_max_usd": "When ticked, when no reference was found and a trade is higher than this amount, we will not accept payment even if it is the only one with this amount.", "enabled": "Whether or not the platform connection is enabled.", } + + +class AdForm(RestrictedFormMixin, ModelForm): + def __init__(self, *args, **kwargs): + super(AdForm, self).__init__(*args, **kwargs) + + class Meta: + model = Ad + fields = ( + "name", + "text", + "payment_details", + "payment_details_real", + "payment_method_details", + "dist_list", + "asset_list", + "provider_list", + "platforms", + "aggregators", + "visible", + "enabled", + ) + help_texts = { + "name": "The name of the aggregator connection.", + "text": "The content of the ad.", + "payment_details": "Shown before a user opens a trade.", + "payment_details_real": "Shown after a user opens a trade.", + "payment_method_details": "Shown in the list", + "dist_list": "Currency and country, space separated, one pair per line.", + "asset_list": "List of assets to distribute ads for.", + "provider_list": "List of providers to distribute ads for.", + "platforms": "Enabled platforms for this ad", + "aggregators": "Enabled aggregators for this ad", + "visible": "Whether or not this ad is visible.", + "enabled": "Whether or not this ad is enabled.", + } + + asset_list = forms.ModelMultipleChoiceField( + queryset=Asset.objects.all(), + widget=forms.CheckboxSelectMultiple, + help_text=Meta.help_texts["asset_list"], + required=True, + ) + provider_list = forms.ModelMultipleChoiceField( + queryset=Provider.objects.all(), + widget=forms.CheckboxSelectMultiple, + help_text=Meta.help_texts["provider_list"], + required=True, + ) + 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, + ) diff --git a/core/lib/money.py b/core/lib/money.py index 1dedff8..0fcc17d 100644 --- a/core/lib/money.py +++ b/core/lib/money.py @@ -30,7 +30,6 @@ class Money(object): Set the logger. Initialise the CoinGecko API. """ - print("MONEY INIT") self.cr = CurrencyRates() self.cg = AsyncCoinGeckoAPISession() auth = (settings.ELASTICSEARCH_USERNAME, settings.ELASTICSEARCH_PASSWORD) @@ -111,7 +110,7 @@ class Money(object): :return: dictionary of USD/XXX rates :rtype: dict """ - rates = await self.cr.get_rates("USD") + rates = self.cr.get_rates("USD") return rates async def get_acceptable_margins(self, platform, currency, amount): @@ -135,18 +134,11 @@ class Money(object): max_local = max_usd * rates[currency] return (min_local, max_local) - async def get_minmax(self, platform, asset, currency): - sets = util.get_settings(platform) + async def get_minmax(self, min_usd, max_usd, asset, currency): rates = await self.get_rates_all() if currency not in rates and not currency == "USD": self.log.error(f"Can't create ad without rates: {currency}") return - if asset == "XMR": - min_usd = float(sets.MinUSDXMR) - max_usd = float(sets.MaxUSDXMR) - elif asset == "BTC": - min_usd = float(sets.MinUSDBTC) - max_usd = float(sets.MaxUSDBTC) if currency == "USD": min_amount = min_usd max_amount = max_usd diff --git a/core/migrations/0010_ad_account_map_ad_aggregators_ad_enabled_and_more.py b/core/migrations/0010_ad_account_map_ad_aggregators_ad_enabled_and_more.py new file mode 100644 index 0000000..a55a011 --- /dev/null +++ b/core/migrations/0010_ad_account_map_ad_aggregators_ad_enabled_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.7 on 2023-03-10 00:51 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0009_asset_provider_ad'), + ] + + operations = [ + migrations.AddField( + model_name='ad', + name='account_map', + field=models.JSONField(default=dict), + ), + migrations.AddField( + model_name='ad', + name='aggregators', + field=models.ManyToManyField(to='core.aggregator'), + ), + migrations.AddField( + model_name='ad', + name='enabled', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='ad', + name='platforms', + field=models.ManyToManyField(to='core.platform'), + ), + ] diff --git a/core/migrations/0011_ad_visible.py b/core/migrations/0011_ad_visible.py new file mode 100644 index 0000000..fe9e072 --- /dev/null +++ b/core/migrations/0011_ad_visible.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.7 on 2023-03-10 02:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0010_ad_account_map_ad_aggregators_ad_enabled_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='ad', + name='visible', + field=models.BooleanField(default=True), + ), + ] diff --git a/core/models.py b/core/models.py index fb97414..08ec346 100644 --- a/core/models.py +++ b/core/models.py @@ -55,7 +55,7 @@ class Aggregator(models.Model): enabled = models.BooleanField(default=True) def __str__(self): - return f"Aggregator ({self.service}) for {self.user}" + return f"{self.name} ({self.get_service_display()})" @classmethod def get_by_id(cls, obj_id, user): @@ -122,6 +122,9 @@ class Platform(models.Model): enabled = models.BooleanField(default=True) + def __str__(self): + return self.name + @classmethod def get_for_user(cls, user): return cls.objects.filter(user=user, enabled=True) @@ -134,16 +137,55 @@ class Platform(models.Model): def account_info(self): return Aggregator.get_account_info_for_platform(self) + @property + def ads(self): + """ + Get all ads linked to this platform. + """ + return Ad.objects.filter(user=self.user, enabled=True, platforms=self) + + @property + def ads_assets(self): + """ + Get all the assets of all the ads. + """ + assets = set() + + for ad in self.ads: + for asset in ad.asset_list.all(): + assets.add(asset.code) + + return list(assets) + + @property + def ads_providers(self): + """ + Get all the providers of all the ads. + """ + providers = set() + + for ad in self.ads: + for provider in ad.provider_list.all(): + providers.add(provider.code) + + return list(providers) + class Asset(models.Model): code = models.CharField(max_length=64) name = models.CharField(max_length=255) + def __str__(self): + return f"{self.name} ({self.code})" + class Provider(models.Model): code = models.CharField(max_length=64) name = models.CharField(max_length=255) + def __str__(self): + return f"{self.name} ({self.code})" + class Ad(models.Model): """ @@ -169,19 +211,27 @@ class Ad(models.Model): asset_list = models.ManyToManyField(Asset) provider_list = models.ManyToManyField(Provider) + platforms = models.ManyToManyField(Platform) + aggregators = models.ManyToManyField(Aggregator) + + account_map = models.JSONField(default=dict) + + visible = models.BooleanField(default=True) + enabled = models.BooleanField(default=True) + -# assets = { -# "XMR": "Monero", -# "BTC": "Bitcoin", -# } -# providers = { -# "REVOLUT": "Revolut", -# "NATIONAL_BANK": "Bank transfer", -# } -# for code, name in assets.items(): -# if not Asset.objects.filter(code=code).exists(): -# Asset.objects.create(code=code, name=name) - -# for code, name in providers.items(): -# if not Provider.objects.filter(code=code).exists(): -# Provider.objects.create(code=code, name=name) +assets = { + "XMR": "Monero", + "BTC": "Bitcoin", +} +providers = { + "REVOLUT": "Revolut", + "NATIONAL_BANK": "Bank transfer", +} +for code, name in assets.items(): + if not Asset.objects.filter(code=code).exists(): + Asset.objects.create(code=code, name=name) + +for code, name in providers.items(): + if not Provider.objects.filter(code=code).exists(): + Provider.objects.create(code=code, name=name) diff --git a/core/templates/base.html b/core/templates/base.html index f789875..2189c2b 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -248,11 +248,11 @@ Wallets - - Advert Configuration + + Ads - - Advert Management + + Posted Ads Market Research diff --git a/core/templates/partials/ad-list.html b/core/templates/partials/ad-list.html new file mode 100644 index 0000000..552c4a2 --- /dev/null +++ b/core/templates/partials/ad-list.html @@ -0,0 +1,79 @@ +{% load cache %} +{% load cachalot cache %} +{% get_last_invalidation 'core.Ad' as last %} +{% include 'mixins/partials/notify.html' %} +{# cache 600 objects_ads request.user.id object_list type last #} + + + + + + + + + {% for item in object_list %} + + + + + + + + {% endfor %} + +
idusernameenabledactions
+ + + + + + {{ item.user }}{{ item.name }} + {% if item.enabled %} + + + + {% else %} + + + + {% endif %} + +
+ + +
+
+{# endcache #} \ No newline at end of file diff --git a/core/views/ads.py b/core/views/ads.py index 149b828..6dc71e3 100644 --- a/core/views/ads.py +++ b/core/views/ads.py @@ -1,5 +1,4 @@ from django.contrib.auth.mixins import LoginRequiredMixin -from django.http import HttpResponse from django.shortcuts import render from django.urls import reverse from django.views import View @@ -19,16 +18,64 @@ from core.util import logs from core.views.helpers import synchronize_async_helper +class AdDist(LoginRequiredMixin, OTPRequiredMixin, View): + template_name = "mixins/partials/notify.html" + + def get(self, request): + ads = Ad.objects.filter(user=request.user, enabled=True) + for ad in ads: + for platform in ad.platforms.all(): + run = synchronize_async_helper(AgoraClient(platform)) + synchronize_async_helper(run.dist_countries(ad)) + + context = {"class": "success", "message": "Distributing ads"} + return render(request, self.template_name, context) + + +class AdRedist(LoginRequiredMixin, OTPRequiredMixin, View): + template_name = "mixins/partials/notify.html" + + def get(self, request): + ads = Ad.objects.filter(user=request.user, enabled=True) + for ad in ads: + for platform in ad.platforms.all(): + run = synchronize_async_helper(AgoraClient(platform)) + synchronize_async_helper(run.redist_countries(ad)) + + context = {"class": "success", "message": "Updating ads"} + return render(request, self.template_name, context) + + class AdList(LoginRequiredMixin, OTPRequiredMixin, ObjectList): list_template = "partials/ad-list.html" model = Ad - page_title = "List of ad connections" + page_title = "List of ads" list_url_name = "ads" list_url_args = ["type"] submit_url_name = "ad_create" + def get_context_data(self): + context = super().get_context_data() + self.extra_buttons = [ + { + "url": reverse("ad_dist"), + "action": "distribute", + "method": "get", + "label": "Distribute ads", + "icon": "fa-solid fa-upload", + }, + { + "url": reverse("ad_redist"), + "action": "redistribute", + "method": "get", + "label": "Update ads", + "icon": "fa-solid fa-refresh", + }, + ] + return context + class AdCreate(LoginRequiredMixin, OTPRequiredMixin, ObjectCreate): model = Ad diff --git a/core/views/platforms.py b/core/views/platforms.py index 14f3e77..5b49fab 100644 --- a/core/views/platforms.py +++ b/core/views/platforms.py @@ -36,7 +36,6 @@ class PlatformTrades(LoginRequiredMixin, OTPRequiredMixin, ObjectList): for platform in platforms: run = synchronize_async_helper(AgoraClient(platform)) dash = synchronize_async_helper(run.wrap_dashboard()) - print("DASH", dash) total_trades[platform.name] = dash return total_trades