From b8495231e524d36c8ffe0b441c7e7294527a0156 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Tue, 29 Nov 2022 21:54:27 +0100 Subject: [PATCH] asset: online-quelle als liste --- __init__.py | 3 +- asset.py | 99 +++++++++++++++++++++++++++++--------------- locale/de.po | 28 ++++++++++--- locale/en.po | 24 ++++++++--- onlinesource.py | 84 ++++++++++++++++++++----------------- tests/test_asset.py | 22 +++++----- tests/test_source.py | 5 ++- view/asset_form.xml | 6 +-- 8 files changed, 171 insertions(+), 100 deletions(-) diff --git a/__init__.py b/__init__.py index ff6b8fc..852ae23 100644 --- a/__init__.py +++ b/__init__.py @@ -4,7 +4,7 @@ # full copyright notices and license terms. from trytond.pool import Pool -from .asset import Asset +from .asset import Asset, AssetSourceRel from .rate import Rate from .identifier import Identifier from .cron import Cron @@ -16,6 +16,7 @@ from .diagram import GraphDef, ChartPoint def register(): Pool.register( OnlineSource, + AssetSourceRel, Asset, Rate, Identifier, diff --git a/asset.py b/asset.py index c4ffab5..9c40c3d 100644 --- a/asset.py +++ b/asset.py @@ -78,15 +78,16 @@ class Asset(ModelSQL, ModelView): help='Stock market symbol'), 'get_identifiers', searcher='search_identifier') - updtsource = fields.Many2One(string='Update Source', - help='Select a source for the course update.', - ondelete='SET NULL', model_name='investment.source') + updtsources = fields.Many2Many(string='Update Sources', + help='Select sources for the course update. The course sources are tried until a valid value has been read.', + relation_name='investment.asset_source_rel', + origin='asset', target='source') updtdays = fields.Selection(string='Select days', required=True, selection=sel_updtdays) updttime = fields.Time(string='Time', states={ - 'readonly': ~Bool(Eval('updtsource')), - }, depends=['updtsource']) + 'readonly': ~Bool(Eval('updtsources')), + }, depends=['updtsources']) nextupdate = fields.Function(fields.DateTime(string='Next Update', readonly=True), 'get_nextupdates', searcher='search_nextupdate') @@ -113,12 +114,48 @@ class Asset(ModelSQL, ModelView): readonly=True, digits=(16,digits_percent)), 'get_percentage_change', searcher='search_percentage') + @classmethod + def __register__(cls, module_name): + """ register and migrate + """ + super(Asset, cls).__register__(module_name) + cls.migrate_updtsource(module_name) + @classmethod def __setup__(cls): super(Asset, cls).__setup__() cls._order.insert(0, ('name', 'ASC')) cls._order.insert(0, ('date', 'DESC')) + @classmethod + def migrate_updtsource(cls, module_name): + """ replace 'updtsource' by relation + """ + pool = Pool() + Asset2 = pool.get('investment.asset') + + asset_table = Asset2.__table_handler__(module_name) + if asset_table.column_exist('updtsource'): + AssetSourceRel = pool.get('investment.asset_source_rel') + tab_asset = Asset2.__table__() + cursor = Transaction().connection.cursor() + + query = tab_asset.select( + tab_asset.id, + tab_asset.updtsource, + where = tab_asset.updtsource != None, + ) + cursor.execute(*query) + records = cursor.fetchall() + to_create = [{ + 'asset': x[0], + 'source': x[1], + } for x in records] + + if len(to_create) > 0: + AssetSourceRel.create(to_create) + asset_table.drop_column('updtsource') + @classmethod def view_attributes(cls): return super().view_attributes() + [ @@ -164,11 +201,11 @@ class Asset(ModelSQL, ModelView): """ return 'work' - @fields.depends('updtsource', 'updttime') - def on_change_updtsource(self): + @fields.depends('updtsources', 'updttime') + def on_change_updtsources(self): """ clear time-fields """ - if self.updtsource is None: + if len(self.updtsources) == 0: self.updttime = None else : self.updttime = time(11, 30) @@ -513,15 +550,21 @@ class Asset(ModelSQL, ModelView): """ pool = Pool() Asset = pool.get('investment.asset') + AssetSourceRel = pool.get('investment.asset_source_rel') Rate = pool.get('investment.rate') tab_asset = Asset.__table__() tab_rate = Rate.__table__() + tab_rel = AssetSourceRel.__table__() context = Transaction().context query_date = context.get('qdate', CurrentDate() - Literal(1)) # get last date of rate - tab_date = tab_asset.join(tab_rate, + tab_date = tab_asset.join(tab_rel, + # link to asset-source-relation to check if + # there are online-sources set + condition=tab_rel.asset == tab_asset.id, + ).join(tab_rate, condition=tab_asset.id == tab_rate.asset, type_ = 'LEFT OUTER', ).select( @@ -531,7 +574,6 @@ class Asset(ModelSQL, ModelView): tab_asset.updttime, distinct_on = [tab_asset.id], order_by = [tab_asset.id, tab_rate.date.desc], - where=tab_asset.updtsource != None, ) query = tab_date.select( @@ -768,27 +810,18 @@ class Asset(ModelSQL, ModelView): ]): OnlineSource.update_rate(asset) - @classmethod - def create(cls, vlist): - """ add debit/credit - """ - vlist = [x.copy() for x in vlist] - for values in vlist: - if 'updtsource' in values.keys(): - if values['updtsource'] is None: - values['updttime'] = None - return super(Asset, cls).create(vlist) - - @classmethod - def write(cls, *args): - """ deny update if cashbook.line!='open', - add or update debit/credit - """ - actions = iter(args) - for lines, values in zip(actions, actions): - if 'updtsource' in values.keys(): - if values['updtsource'] is None: - values['updttime'] = None - super(Asset, cls).write(*args) - # end Asset + + +class AssetSourceRel(ModelSQL): + 'Asset Source Relation' + __name__ = 'investment.asset_source_rel' + + source = fields.Many2One(string='Online Source', select=True, + required=True, model_name='investment.source', + ondelete='CASCADE') + asset = fields.Many2One(string='Asset', select=True, + required=True, model_name='investment.asset', + ondelete='CASCADE') + +# end AssetSourceRel diff --git a/locale/de.po b/locale/de.po index c100853..ac567f4 100644 --- a/locale/de.po +++ b/locale/de.po @@ -182,13 +182,13 @@ msgctxt "help:investment.asset,date:" msgid "Date of current rate" msgstr "Datum des aktuellen Kurses" -msgctxt "field:investment.asset,updtsource:" -msgid "Update Source" -msgstr "Kursquelle" +msgctxt "field:investment.asset,updtsources:" +msgid "Update Sources" +msgstr "Kursquellen" -msgctxt "help:investment.asset,updtsource:" -msgid "Select a source for the course update." -msgstr "Wählen Sie eine Quelle für die Kursaktualisierung aus." +msgctxt "help:investment.asset,updtsources:" +msgid "Select sources for the course update. The course sources are tried until a valid value has been read." +msgstr "Wählen Sie Quellen für die Kursaktualisierung aus. Die Kursquellen werden probiert bis ein gültiger Wert gelesen wurde." msgctxt "field:investment.asset,updttime:" msgid "Time" @@ -251,6 +251,22 @@ msgid "Mon - Sun" msgstr "Mo - So" +############################### +# investment.asset-source-rel # +############################### +msgctxt "model:investment.asset_source_rel,name:" +msgid "Asset Source Relation" +msgstr "Vermögenswert Quelle Verknüpfung" + +msgctxt "field:investment.asset_source_rel,source:" +msgid "Source" +msgstr "Quelle" + +msgctxt "field:investment.asset_source_rel,asset:" +msgid "Asset" +msgstr "Vermögenswert" + + ##################### # investment.source # ##################### diff --git a/locale/en.po b/locale/en.po index 22530ee..f35f434 100644 --- a/locale/en.po +++ b/locale/en.po @@ -154,13 +154,13 @@ msgctxt "help:investment.asset,date:" msgid "Date of current rate" msgstr "Date of current rate" -msgctxt "field:investment.asset,updtsource:" -msgid "Update Source" -msgstr "Update Source" +msgctxt "field:investment.asset,updtsources:" +msgid "Update Sources" +msgstr "Update Sources" -msgctxt "help:investment.asset,updtsource:" -msgid "Select a source for the course update." -msgstr "Select a source for the course update." +msgctxt "help:investment.asset,updtsources:" +msgid "Select sources for the course update. The course sources are tried until a valid value has been read." +msgstr "Select sources for the course update. The course sources are tried until a valid value has been read." msgctxt "field:investment.asset,updttime:" msgid "Time" @@ -222,6 +222,18 @@ msgctxt "selection:investment.asset,updtdays:" msgid "Mon - Sun" msgstr "Mon - Sun" +msgctxt "model:investment.asset_source_rel,name:" +msgid "Asset Source Relation" +msgstr "Asset Source Relation" + +msgctxt "field:investment.asset_source_rel,source:" +msgid "Source" +msgstr "Source" + +msgctxt "field:investment.asset_source_rel,asset:" +msgid "Asset" +msgstr "Asset" + msgctxt "model:investment.source,name:" msgid "Online Source" msgstr "Online Source" diff --git a/onlinesource.py b/onlinesource.py index 125cf6b..5f551cc 100644 --- a/onlinesource.py +++ b/onlinesource.py @@ -222,48 +222,56 @@ class OnlineSource(ModelSQL, ModelView): """ pool = Pool() Rate = pool.get('investment.rate') + IrDate = pool.get('ir.date') - if asset.updtsource is None: + if len(asset.updtsources) == 0: return - rate_data = cls.read_from_website( - asset.updtsource, - isin = asset.isin, - nsin = asset.wkn, - symbol = asset.secsymb, - ) - if len(asset.updtsource.rgxident or '') > 0: - # check result - same code? - code = rate_data.get('code', None) - if code: - asset_code = getattr(asset, { - 'isin': 'isin', - 'nsin': 'wkn', - 'symbol': 'secsymb', - }[asset.updtsource.rgxidtype]) - if (asset_code or '').lower() != code.lower(): - # fail - logger.warning( - 'update_rate: got wrong code "%(wrong)s" - expected "%(exp)s"' % { - 'exp': asset_code, - 'wrong': code, - }) - return False + for updtsource in asset.updtsources: + rate_data = cls.read_from_website( + updtsource, + isin = asset.isin, + nsin = asset.wkn, + symbol = asset.secsymb, + ) - to_create = { - 'date': rate_data.get('date', None), - 'rate': rate_data.get('rate', None), - 'asset': asset.id, - } - if (to_create['date'] is not None) and \ - (to_create['rate'] is not None): - # check if exists - if Rate.search_count([ - ('asset.id', '=', asset.id), - ('date', '=', to_create['date']), - ]) == 0: - Rate.create([to_create]) - return True + if len(updtsource.rgxident or '') > 0: + # check result - same code? + code = rate_data.get('code', None) + if code: + asset_code = getattr(asset, { + 'isin': 'isin', + 'nsin': 'wkn', + 'symbol': 'secsymb', + }[updtsource.rgxidtype]) + if (asset_code or '').lower() != code.lower(): + # fail + logger.warning( + 'update_rate: got wrong code "%(wrong)s" - expected "%(exp)s"' % { + 'exp': asset_code, + 'wrong': code, + }) + continue + + to_create = { + 'date': rate_data.get('date', None), + 'rate': rate_data.get('rate', None), + 'asset': asset.id, + } + if (to_create['date'] is not None) and \ + (to_create['rate'] is not None): + # check if exists + if Rate.search_count([ + ('asset.id', '=', asset.id), + ('date', '=', to_create['date']), + ]) == 0: + Rate.create([to_create]) + return True + else : + # if we got a record for today - stop + # otherwise try next source + if to_create['date'] == IrDate.today(): + break return False def get_regex_result(self, html_text, field_name): diff --git a/tests/test_asset.py b/tests/test_asset.py index 1d65593..798518f 100644 --- a/tests/test_asset.py +++ b/tests/test_asset.py @@ -568,18 +568,19 @@ class AssetTestCase(ModuleTestCase): 'name': 'Source 1', }]) - self.assertEqual(asset.updtsource, None) + self.assertEqual(len(asset.updtsources), 0) self.assertEqual(asset.updttime, time(14,0)) - asset.updtsource = o_source + asset.updtsources = [o_source] asset.updttime = time(10, 45) asset.save() - self.assertEqual(asset.updtsource.rec_name, 'Source 1') + self.assertEqual(len(asset.updtsources), 1) + self.assertEqual(asset.updtsources[0].rec_name, 'Source 1') self.assertEqual(asset.updttime, time(10, 45)) - asset.updtsource = None - asset.on_change_updtsource() - self.assertEqual(asset.updtsource, None) + asset.updtsources = [] + asset.on_change_updtsources() + self.assertEqual(len(asset.updtsources), 0) self.assertEqual(asset.updttime, None) @with_transaction() @@ -606,7 +607,7 @@ class AssetTestCase(ModuleTestCase): Asset.write(*[ [asset], { - 'updtsource': o_source.id, + 'updtsources': [('add', [o_source.id])], 'updttime': time(10, 45), }]) @@ -616,7 +617,8 @@ class AssetTestCase(ModuleTestCase): # re-read to make context work asset2, = Asset.browse([asset.id]) - self.assertEqual(asset2.updtsource.rec_name, 'Source 1') + self.assertEqual(len(asset2.updtsources), 1) + self.assertEqual(asset2.updtsources[0].rec_name, 'Source 1') self.assertEqual(asset2.updttime, time(10, 45)) self.assertEqual(len(asset2.rates), 0) # qdate = 2022-10-14 simulates existence of record at this day @@ -643,7 +645,7 @@ class AssetTestCase(ModuleTestCase): self.assertEqual(len(asset.rates), 1) asset2, = Asset.browse([asset.id]) - self.assertEqual(asset.updtsource.rec_name, 'Source 1') + self.assertEqual(asset.updtsources[0].rec_name, 'Source 1') self.assertEqual(asset.updttime, time(10, 45)) self.assertEqual(len(asset.rates), 1) self.assertEqual(asset.rates[0].date, date(2022, 10, 17)) @@ -668,7 +670,7 @@ class AssetTestCase(ModuleTestCase): self.assertEqual(len(asset.rates), 2) asset2, = Asset.browse([asset.id]) - self.assertEqual(asset2.updtsource.rec_name, 'Source 1') + self.assertEqual(asset2.updtsources[0].rec_name, 'Source 1') self.assertEqual(asset2.updttime, time(10, 45)) self.assertEqual(len(asset2.rates), 2) self.assertEqual(asset2.rates[0].date, date(2022, 10, 18)) diff --git a/tests/test_source.py b/tests/test_source.py index 0ce1e92..630f4be 100644 --- a/tests/test_source.py +++ b/tests/test_source.py @@ -64,7 +64,7 @@ class SourceTestCase(ModuleTestCase): Asset.write(*[ [asset], { - 'updtsource': osource.id, + 'updtsources': [('add', [osource.id])], }]) with Transaction().set_context({ @@ -76,7 +76,8 @@ class SourceTestCase(ModuleTestCase): self.assertEqual(asset2.isin, 'XC0009655157') self.assertEqual(asset2.secsymb, '1472977') self.assertEqual(asset2.updttime, time(14, 0)) - self.assertEqual(asset2.updtsource.rec_name, 'Source 1') + self.assertEqual(len(asset2.updtsources), 1) + self.assertEqual(asset2.updtsources[0].rec_name, 'Source 1') self.assertEqual(asset2.updtdays, 'work') self.assertEqual(asset2.nextupdate, datetime(2022, 10, 3, 14, 0)) self.assertEqual(len(asset.rates), 0) diff --git a/view/asset_form.xml b/view/asset_form.xml index ec517b7..081304e 100644 --- a/view/asset_form.xml +++ b/view/asset_form.xml @@ -57,14 +57,12 @@ full copyright notices and license terms. --> -