Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added coingecko modules #72

Merged
merged 1 commit into from
Nov 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions currency_rate_update_coingecko/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from . import models
17 changes: 17 additions & 0 deletions currency_rate_update_coingecko/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2023 Tecnativa - Ernesto Tejeda
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
"name": "Currency Rate Update: CoinGecko",
"version": "16.0.1.0.0",
"category": "Financial Management/Configuration",
"summary": "Update exchange rates using CoinGecko",
"author": "Tecnativa, Odoo Community Association (OCA)",
"website": "https://www.onestein.nl",
"license": "AGPL-3",
"installable": True,
"application": False,
"depends": ["currency_rate_update_mapping", "crypto_currency"],
"data": ["data/res_currency_rate_provider.xml"],
"external_dependencies": {"python": ["pycgapi"]},
}
10 changes: 10 additions & 0 deletions currency_rate_update_coingecko/data/res_currency_rate_provider.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" ?>
<!--
Copyright 2024 Onestein
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo noupdate="1">
<record model="res.currency.rate.provider" id="res_currency_rate_provider_coingecko">
<field name="service">CoinGecko</field>
</record>
</odoo>
2 changes: 2 additions & 0 deletions currency_rate_update_coingecko/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from . import res_currency_rate_provider_CoinGecko
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright 2024 Onestein
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
import logging
from datetime import date, timedelta

from pycgapi import CoinGeckoAPI

from odoo import _, fields, models

_logger = logging.getLogger(__name__)


class ResCurrencyRateProviderCoinGecko(models.Model):
_inherit = "res.currency.rate.provider"

service = fields.Selection(
selection_add=[("CoinGecko", "CoinGecko")],
ondelete={"CoinGecko": "set default"},
)

def _get_supported_currencies(self):
self.ensure_one()
if self.service != "CoinGecko":
return super()._get_supported_currencies()
# List of cryptocurrencies based on configured provider mappings
supported_currencies = (
self.env["res.currency.rate.provider.mapping"]
.search([("provider_id", "=", self.id)])
.mapped("currency_id.name")
)
return supported_currencies

def _obtain_rates(self, base_currency, currencies, date_from, date_to):
self.ensure_one()
if self.service != "CoinGecko":
return super()._obtain_rates(base_currency, currencies, date_from, date_to)
if date_from < date.today():
return self._get_historical_rate_from_coingecko(
date_from, date_to, base_currency
)
else:
return self._get_latest_rate_from_coingecko(base_currency)

def _get_latest_rate_from_coingecko(self, base_currency):
"""Get all the exchange rates for today"""
api = CoinGeckoAPI()
today = date.today()
data = {today: {}}
for (
currency
) in self.currency_ids.res_currency_rate_provider_mapping_ids.filtered(
lambda l: l.provider_id == self
):
try:
coin_data = api.coin_historical_on_date(
currency.provider_reference, today.strftime("%m-%d-%Y")
)
except Exception as e:
_logger.warning(
'Currency Rate Provider "%(name)s" failed to obtain for %(currency)s currency'
% {
"name": self.name,
"currency": currency.currency_id.name,
},
exc_info=True,
)
self.message_post(
subject=_("Currency Rate Provider Failure"),
body=_(
'Currency Rate Provider "%(name)s" failed to obtain data(check the rate provider mapping on the currency) :\n%(error)s'
)
% {
"name": self.name,
"currency": currency.currency_id.name,
"error": str(e) if e else _("N/A"),
},
)
continue
rate = (
coin_data.get("market_data")
.get("current_price")
.get(base_currency.lower(), 0)
)
if rate:
data[today].update({currency.currency_id.name: 1 / rate})
return data

def _get_historical_rate_from_coingecko(self, date_from, date_to, base_currency):
"""Get all the exchange rates from 'date_from' to 'date_to'"""
api = CoinGeckoAPI()
content = {}
current_date = date_from
while current_date <= date_to:
content[current_date] = {}
for (
currency
) in self.currency_ids.res_currency_rate_provider_mapping_ids.filtered(
lambda l: l.provider_id == self
):
try:
coin_data = api.coin_historical_on_date(
currency.provider_reference, current_date.strftime("%m-%d-%Y")
)
except Exception as e:
_logger.warning(
'Currency Rate Provider "%(name)s" failed to obtain for %(currency)s currency'
% {
"name": self.name,
"currency": currency.currency_id.name,
},
exc_info=True,
)
self.message_post(
subject=_("Currency Rate Provider Failure"),
body=_(
'Currency Rate Provider "%(name)s" failed to obtain data(check the rate provider mapping on the currency) :\n%(error)s'
)
% {
"name": self.name,
"currency": currency.currency_id.name,
"error": str(e) if e else _("N/A"),
},
)
continue
rate = (
coin_data.get("market_data")
.get("current_price")
.get(base_currency.lower(), 0)
)
if rate:
content[current_date].update({currency.currency_id.name: 1 / rate})
current_date += timedelta(days=1)
return content
3 changes: 3 additions & 0 deletions currency_rate_update_coingecko/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from . import test_currency_rate_update_coingecko
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright 2024 Onestein
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from dateutil.relativedelta import relativedelta

from odoo import fields
from odoo.tests import common, tagged
from odoo.tools import mute_logger


@tagged("post_install", "-at_install")
class TestResCurrencyRateProviderCoinGecko(common.TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()

cls.Company = cls.env["res.company"]
cls.CurrencyRate = cls.env["res.currency.rate"]
cls.Currency = cls.env["res.currency"]
cls.CurrencyRateProvider = cls.env["res.currency.rate.provider"]
cls.CurrencyRateProviderMapping = cls.env["res.currency.rate.provider.mapping"]

cls.today = fields.Date.today()
cls.eur_currency = cls.env.ref("base.EUR")
cls.company = cls.Company.create(
{"name": "Test company", "currency_id": cls.eur_currency.id}
)
cls.lnk_currency = cls.Currency.create({"name": "LINK", "symbol": "LNK"})
cls.coingecko_provider = cls.CurrencyRateProvider.search(
[("service", "=", "CoinGecko")], limit=1
)
cls.coingecko_provider_mapping = cls.CurrencyRateProviderMapping.create(
{
"currency_id": cls.lnk_currency.id,
"provider_id": cls.coingecko_provider.id,
"provider_reference": "chainlink",
}
)
cls.coingecko_provider.write(
{
"currency_ids": [
(4, cls.lnk_currency.id),
],
}
)
cls.env.user.company_ids += cls.company
cls.env.company = cls.company
cls.CurrencyRate.search([]).unlink()

def test_supported_currencies_CoinGecko(self):
self.coingecko_provider._get_supported_currencies()

def test_cron(self):
self.coingecko_provider._scheduled_update()
rates = self.CurrencyRate.search([])
self.assertTrue(rates)
self.assertEqual(rates.currency_id, self.lnk_currency)
self.CurrencyRate.search([("company_id", "=", self.company.id)]).unlink()

def test_wizard(self):
wizard = (
self.env["res.currency.rate.update.wizard"]
.with_context(
default_provider_ids=[(6, False, self.coingecko_provider.ids)]
)
.create({})
)
wizard.action_update()
rates = self.CurrencyRate.search([])
self.assertTrue(rates)
self.assertEqual(rates.currency_id, self.lnk_currency)
self.CurrencyRate.search([("company_id", "=", self.company.id)]).unlink()

@mute_logger(
"odoo.addons.currency_rate_update_coingecko.models.res_currency_rate_provider_CoinGecko"
)
def test_error_CoinGecko(self):
self.coingecko_provider_mapping.write({"provider_reference": "test"})
self.coingecko_provider._update(self.today, self.today)
rates = self.CurrencyRate.search([])
self.assertEqual(len(rates), 0)
self.coingecko_provider._update(self.today - relativedelta(days=2), self.today)
rates = self.CurrencyRate.search([])
self.assertEqual(len(rates), 0)
3 changes: 3 additions & 0 deletions currency_rate_update_mapping/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import models
17 changes: 17 additions & 0 deletions currency_rate_update_mapping/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright 2024 Onestein
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Currency Rate Update Mapping",
"version": "16.0.1.0.0",
"author": "Onestein BV",
"website": "https://www.onestein.nl",
"license": "AGPL-3",
"category": "Financial Management/Configuration",
"summary": "Allows to add mappings for currency rate providers",
"depends": ["currency_rate_update"],
"data": [
"security/ir.model.access.csv",
"views/res_currency.xml",
],
"installable": True,
}
4 changes: 4 additions & 0 deletions currency_rate_update_mapping/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from . import res_currency
from . import res_currency_rate_provider_mapping
16 changes: 16 additions & 0 deletions currency_rate_update_mapping/models/res_currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2024 Onestein
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import fields, models


class ResCurrency(models.Model):
_inherit = "res.currency"

res_currency_rate_provider_mapping_ids = fields.One2many(
comodel_name="res.currency.rate.provider.mapping",
inverse_name="currency_id",
string="Currency Rate Provider Mappings",
copy=False,
help="Currency mapping with the rate provider for updating rates",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2024 Onestein
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models


class ResCurrencyRateProviderMapping(models.Model):
_name = "res.currency.rate.provider.mapping"
_description = "Currency Rate Provider Mapping"

currency_id = fields.Many2one(
string="Currency",
comodel_name="res.currency",
)
provider_id = fields.Many2one(
string="Provider",
comodel_name="res.currency.rate.provider",
ondelete="restrict",
)
provider_reference = fields.Char(
required=True,
help="Defines the reference to be used when fetching rates from the provider",
)
3 changes: 3 additions & 0 deletions currency_rate_update_mapping/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_res_currency_rate_update_mapping_service_admin,res.currency.rate.provider.mapping,model_res_currency_rate_provider_mapping,base.group_system,1,1,1,1
access_res_currency_rate_update_mapping_service_manager,res.currency.rate.provider.mapping,model_res_currency_rate_provider_mapping,account.group_account_manager,1,0,0,0
25 changes: 25 additions & 0 deletions currency_rate_update_mapping/views/res_currency.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2024 Onestein
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-->
<odoo>
<record model="ir.ui.view" id="view_currency_form">
<field name="name">res.currency.form</field>
<field name="model">res.currency</field>
<field name="inherit_id" ref="base.view_currency_form"/>
<field name="arch" type="xml">
<notebook position="inside">
<page string="Rate Provider Mappings" name="rate_provider_mappings">
<field name="res_currency_rate_provider_mapping_ids" widget="one2many">
<tree editable="top" limit="25">
<field name="provider_id"/>
<field name="provider_reference"/>
</tree>
</field>
</page>
</notebook>

</field>
</record>
</odoo>
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ xlrd
python-slugify
vcrpy
dnspython==2.6.1
pycgapi
Loading