Compare commits

..

64 commits

Author SHA1 Message Date
Frederik Jaeckel
4a3d996600 optimize code 2024-01-21 19:37:48 +01:00
Frederik Jaeckel
ab6ab32d0a update online source 'www.sbroker.de' 2024-01-21 19:14:05 +01:00
Frederik Jaeckel
4a2135512f accept http 410 on server call 2024-01-21 19:08:41 +01:00
Frederik Jaeckel
9f74b8fbf7 check regex-code when save online-source 2024-01-21 18:55:21 +01:00
Frederik Jaeckel
c2df388692 asset: columns optional 2023-12-03 18:20:22 +01:00
Frederik Jaeckel
18ef1aaeb6 Tryton 7.0 2023-12-01 13:31:43 +01:00
Frederik Jaeckel
faefea3b5f formatting 2023-12-01 13:29:46 +01:00
Frederik Jaeckel
aed948fb8a asset-list: remove percent-symbol to speed up list view 2023-06-23 21:40:55 +02:00
Frederik Jaeckel
ff5893b451 diagram: simplify queries 2023-06-23 16:41:39 +02:00
Frederik Jaeckel
6e77058946 asset: avoid exceptions in get_identifiers() 2023-06-23 16:29:10 +02:00
Frederik Jaeckel
38e2c34a53 asset/rate: speed up percent-queries 2023-06-23 16:02:31 +02:00
Frederik Jaeckel
3b9de6c0bb asset: fixed possible exceptions 2023-06-22 17:28:58 +02:00
Frederik Jaeckel
ddffa302c4 remove: graph_form.xml 2023-06-07 21:57:27 +02:00
Frederik Jaeckel
1c5b31d2f1 Tryton 6.8: tests, indexes, info 2023-06-07 21:52:09 +02:00
Frederik Jaeckel
765738d9ee test: add more context 2023-06-07 20:06:36 +02:00
Frederik Jaeckel
ebb6d71be3 test: add context to product-create 2023-06-07 19:24:43 +02:00
Frederik Jaeckel
8f12741d76 formatting 2023-06-07 18:44:53 +02:00
Frederik Jaeckel
82f9d3a792 asset/onlinesource: add fixed url,
pre defned online sources: add FAZ.net
2023-04-21 16:55:51 +02:00
Frederik Jaeckel
edfa0424e5 online source: formatting 2023-04-21 15:40:07 +02:00
Frederik Jaeckel
26192ea831 add online-source www.finanzen.net - Aktie 2023-04-21 15:31:10 +02:00
Frederik Jaeckel
fbbb7bd96c setup.py 2023-02-28 09:13:29 +01:00
Frederik Jaeckel
6f1b3707bd readme updated 2023-02-28 09:05:52 +01:00
Frederik Jaeckel
23665ae6dc removed unused imports 2023-01-13 13:48:50 +01:00
Frederik Jaeckel
6789e63872 OnlineSource: Query method prepared for external extension 2023-01-13 13:08:52 +01:00
Frederik Jaeckel
40ef3ef192 remove caching, optimize rate/query 2023-01-10 19:54:32 +01:00
Frederik Jaeckel
0daf39b913 asset: cachezeit verkürzt 2023-01-09 22:21:03 +01:00
Frederik Jaeckel
8503fd3e32 caching für change_day1/month1/... 2023-01-08 20:56:27 +01:00
Frederik Jaeckel
8cfa54f693 asset: abfragezeit für prozente optimiert 2023-01-07 16:26:22 +01:00
Frederik Jaeckel
3dc79fc292 rate: optimize for speed 2023-01-05 22:25:16 +01:00
Frederik Jaeckel
8a4c8fa58f asset: Feld 'change_symbol' für Prozenzzeichen + test 2023-01-04 20:20:27 +01:00
Frederik Jaeckel
166a9e13a9 asset: spalte 'einheit' entfällt, Spalte 'Kurs' hat nun die Einheit 2023-01-04 17:12:46 +01:00
Frederik Jaeckel
d9403e4946 quellen korrigiert 2022-12-24 12:38:10 +01:00
Frederik Jaeckel
935be70112 setup - read_file 2022-12-20 12:31:36 +01:00
Frederik Jaeckel
347f09b49e test für wizard 2022-12-19 21:53:00 +01:00
Frederik Jaeckel
77d6e5f8ef import wizard 2022-12-19 21:13:32 +01:00
Frederik Jaeckel
94687139b1 onlinesource: sortierung, neues datumsformat 'dd.mm.yy',
neue quellen: finanzen.net/Stuttgard, s-broker
2022-12-18 13:40:13 +01:00
Frederik Jaeckel
94169fb416 asset: rekursion behoben, erlaubt aktion nach update 2022-12-16 14:17:28 +01:00
Frederik Jaeckel
ac0bced963 updates an asset nach online-aktion 2022-12-16 13:37:36 +01:00
Frederik Jaeckel
fcabafa080 asset: Feld 'symbol' in übersetzter form 2022-12-09 09:43:30 +01:00
Frederik Jaeckel
e30953a55c asset: prozentwerte mit einheit im form 2022-12-06 08:53:40 +01:00
Frederik Jaeckel
407569a684 asset: currency_digits limit --> 6 2022-12-05 22:34:51 +01:00
Frederik Jaeckel
9061039ec2 asset: bereichsgrenzen für currency_digits, farbe rot für tagesnegativ 2022-12-05 21:02:17 +01:00
Frederik Jaeckel
69afec62b0 import-script: add field-delimiter 2022-12-05 09:49:59 +01:00
Frederik Jaeckel
eb4af774c0 symbol fix 2022-12-03 00:08:55 +01:00
Frederik Jaeckel
e972d54f54 felder symbol, name, product_uom optimiert,
felder company_currency entfernt
2022-12-02 23:29:01 +01:00
Frederik Jaeckel
23d4790e3b icon 2022-12-02 14:58:54 +01:00
Frederik Jaeckel
71b52d106a asset: liste mit reiter für aktuell/inaktiv/alle 2022-12-02 12:56:14 +01:00
Frederik Jaeckel
f4310c596a asset: rec_name - einheit als währung/menge 2022-12-01 16:55:27 +01:00
Frederik Jaeckel
de99f0e473 asset: einheit als währung/menge 2022-12-01 16:30:50 +01:00
Frederik Jaeckel
c8c2afb190 icons 2022-11-30 21:50:31 +01:00
Frederik Jaeckel
78fc16d7e2 fix imports 2022-11-30 13:59:20 +01:00
Frederik Jaeckel
cc5a2fd164 setup.py 2022-11-30 13:35:21 +01:00
Frederik Jaeckel
9c5af080f5 importscript für historische kurse 2022-11-30 13:32:16 +01:00
Frederik Jaeckel
258c14e2b5 asset: online-quelle als liste 2022-11-29 21:54:27 +01:00
Frederik Jaeckel
7334e53f9f diagram: interpolator ergänzt,
asset: berechnung 'nextupdate' korrigiert
2022-11-28 23:15:28 +01:00
Frederik Jaeckel
d08cf19b60 asset: sortierung - neueste nach oben, farben - älter als 5 tage = gedimmt 2022-11-28 15:44:07 +01:00
Frederik Jaeckel
1a61b112e0 asset:tabellenzugriff optimiert,
diagram: darstellung in diagramm ergänzt
2022-11-26 22:42:02 +01:00
Frederik Jaeckel
83acfbb14b asset: suche in name, sortiert nach name, sortierer für wkn, isin, symbl 2022-11-25 22:46:32 +01:00
Frederik Jaeckel
82c451377c asset: spalten tag/monat/3monate... ok + test 2022-11-25 21:55:43 +01:00
Frederik Jaeckel
d70e674e58 abfrage der prozentwerte für tag/1 monat, 3 monate, 6 monate, 12 monate
test muß noch
2022-11-25 15:28:49 +01:00
Frederik Jaeckel
cba2117d13 asset: abfrage updatezeitpunkt optimiert, anzeige der vortagsprozente, farbe für zeilen 2022-11-25 11:00:03 +01:00
Frederik Jaeckel
34faf91476 prozentwerte begonnen 2022-11-24 23:17:44 +01:00
Frederik Jaeckel
f323334800 bug in updtneeded-suche, online-quellen erweitert 2022-11-23 22:22:22 +01:00
Frederik Jaeckel
1daa743631 asset: feld 'Datum' + 'Name' neu, rec_name optimiert, Test korrigiert 2022-11-23 10:44:09 +01:00
17 changed files with 213 additions and 255 deletions

View file

@ -9,7 +9,7 @@ pip install mds-investment
Requires Requires
======== ========
- Tryton 6.0 - Tryton 7.0
How to How to
====== ======
@ -22,114 +22,6 @@ You can define the course sources yourself.
Changes Changes
======= =======
*6.0.25 - 01.12.2023* *7.0.0 - 01.12.2023*
- code/speed optimized - compatibility to Tryton 7.0
*6.0.24 - 07.06.2023*
- code optimized
*6.0.23 - 21.01.2023*
- updt: online-source optimized for external extension
*6.0.22 - 10.01.2023*
- updt: optimze rate, remove caching
*6.0.21 - 08.01.2023*
- add: caching for percentual changes
*6.0.20 - 07.01.2023*
- updt: asset - percent-values optimize for speed
*6.0.19 - 05.01.2023*
- updt: rate - optimize for speed
*6.0.18 - 04.01.2023*
- add: units to lists
- updt: online-sources
*6.0.17 - 19.12.2022*
- add: import wizard
*6.0.16 - 18.12.2022*
- add: onlinesource - sorting, sources
*6.0.15 - 16.12.2022*
- fix: recoursion
- add: enable after-update actions
*6.0.14 - 09.12.2022*
- updt: translated symbol of asset-currency
*6.0.13 - 06.12.2022*
- updt: asset-form - units for percent-values, asset-list - day-negative in red-color
*6.0.12 - 05.12.2022*
- import-script - add field-delimter to param-list
*6.0.11 - 03.12.2022*
- add: tabs for asset-list, new icons, queries optimized
*6.0.10 - 01.12.2022*
- updt: optimized asset-list, unit as symbol
*6.0.9 - 30.11.2022*
- add: script to import historical rates
*6.0.8 - 29.11.2022*
- add: online-source as list to allow multiple sources
for asset
*6.0.7 - 28.11.2022*
- fix: add diagram-interpolate
- fix: corrected nextupdate
*6.0.6 - 26.11.2022*
- add: diagram-display
*6.0.5 - 25.11.2022*
- add: sorter for name/nsin/isin/symbol
- add: columns percentage by day/month/3month/6month/12month
- fix: online-updater
*6.0.4 - 25.11.2022*
- add: assets - colors for percentual success/loss
- updt: optimize timestamp for next online-update
*6.0.3 - 23.11.2022*
- fix: bug in searcher
- add: online-sources
*6.0.2 - 23.11.2022*
- asset: add field 'date', optimized 'rec_name'
*6.0.1 - 22.11.2022*
- works
*6.0.0 - 09.11.2022*
- init

117
asset.py
View file

@ -3,7 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the # The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields, SymbolMixin from trytond.model import ModelView, ModelSQL, fields, SymbolMixin, Index
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval, Bool, If, Date from trytond.pyson import Eval, Bool, If, Date
@ -32,7 +32,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
name = fields.Function(fields.Char( name = fields.Function(fields.Char(
string='Name', readonly=True), string='Name', readonly=True),
'get_name_symbol', searcher='search_rec_name') 'get_name_symbol', searcher='search_rec_name')
company = fields.Many2One( company = fields.Many2One(
string='Company', model_name='company.company', string='Company', model_name='company.company',
required=True, ondelete="RESTRICT") required=True, ondelete="RESTRICT")
@ -46,12 +46,9 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
uom = fields.Many2One( uom = fields.Many2One(
string='UOM', required=True, model_name='product.uom', string='UOM', required=True, model_name='product.uom',
ondelete='RESTRICT', ondelete='RESTRICT',
states={ states={'readonly': ~Bool(Eval('product'))},
'readonly': ~Bool(Eval('product')), domain=[('category', '=', Eval('product_uom'))],
}, depends=['product_uom', 'product'])
domain=[
('category', '=', Eval('product_uom')),
], depends=['product_uom', 'product'])
symbol = fields.Function(fields.Char( symbol = fields.Function(fields.Char(
string='UOM', readonly=True), 'get_name_symbol', string='UOM', readonly=True), 'get_name_symbol',
searcher='search_uom_symbol') searcher='search_uom_symbol')
@ -70,13 +67,11 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
'get_rate_data', searcher='search_date') 'get_rate_data', searcher='search_date')
currency = fields.Many2One( currency = fields.Many2One(
string='Currency', select=True, required=True, string='Currency', required=True,
model_name='currency.currency', ondelete='RESTRICT') model_name='currency.currency', ondelete='RESTRICT')
currency_digits = fields.Integer( currency_digits = fields.Integer(
string='Digits', required=True, string='Digits', required=True,
domain=[ domain=[('currency_digits', '>=', 0), ('currency_digits', '<=', 6)])
('currency_digits', '>=', 0),
('currency_digits', '<=', 6)])
wkn = fields.Function(fields.Char( wkn = fields.Function(fields.Char(
string='NSIN', readonly=True, string='NSIN', readonly=True,
@ -112,9 +107,8 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
string='Select days', required=True, selection=sel_updtdays) string='Select days', required=True, selection=sel_updtdays)
updttime = fields.Time( updttime = fields.Time(
string='Time', string='Time',
states={ states={'readonly': ~Bool(Eval('updtsources'))},
'readonly': ~Bool(Eval('updtsources')), depends=['updtsources'])
}, depends=['updtsources'])
nextupdate = fields.Function(fields.DateTime( nextupdate = fields.Function(fields.DateTime(
string='Next Update', readonly=True), string='Next Update', readonly=True),
'get_nextupdates', searcher='search_nextupdate') 'get_nextupdates', searcher='search_nextupdate')
@ -160,6 +154,21 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
super(Asset, cls).__setup__() super(Asset, cls).__setup__()
cls._order.insert(0, ('name', 'ASC')) cls._order.insert(0, ('name', 'ASC'))
cls._order.insert(0, ('date', 'DESC')) cls._order.insert(0, ('date', 'DESC'))
t = cls.__table__()
cls._sql_indexes.update({
Index(
t,
(t.product, Index.Equality())),
Index(
t,
(t.currency, Index.Equality())),
Index(
t,
(t.uom, Index.Equality())),
Index(
t,
(t.updtdays, Index.Equality())),
})
@classmethod @classmethod
def migrate_updtsource(cls, module_name): def migrate_updtsource(cls, module_name):
@ -186,7 +195,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
'source': x[1], 'source': x[1],
} for x in records] } for x in records]
if len(to_create) > 0: if to_create:
AssetSourceRel.create(to_create) AssetSourceRel.create(to_create)
asset_table.drop_column('updtsource') asset_table.drop_column('updtsource')
@ -247,7 +256,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
def on_change_updtsources(self): def on_change_updtsources(self):
""" clear time-fields """ clear time-fields
""" """
if len(self.updtsources) == 0: if not self.updtsources:
self.updttime = None self.updttime = None
else: else:
self.updttime = time(11, 30) self.updttime = time(11, 30)
@ -408,8 +417,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select( query = tab_query.select(
tab_query.id, tab_query.id,
where=Operator(tab_query.date, clause[2]), where=Operator(tab_query.date, clause[2]))
)
return [('id', 'in', query)] return [('id', 'in', query)]
@classmethod @classmethod
@ -421,8 +429,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select( query = tab_query.select(
tab_query.id, tab_query.id,
where=Operator(tab_query.rate, clause[2]), where=Operator(tab_query.rate, clause[2]))
)
return [('id', 'in', query)] return [('id', 'in', query)]
@staticmethod @staticmethod
@ -434,8 +441,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select( query = tab_query.select(
tab_query.date, tab_query.date,
where=tab_query.id == table.id, where=tab_query.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -447,8 +453,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select( query = tab_query.select(
tab_query.rate, tab_query.rate,
where=tab_query.id == table.id, where=tab_query.id == table.id)
)
return [query] return [query]
@classmethod @classmethod
@ -474,8 +479,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_rate1.rate, tab_rate1.rate,
distinct_on=[tab_rate1.asset], distinct_on=[tab_rate1.asset],
order_by=[tab_rate1.asset, tab_rate1.date.desc], order_by=[tab_rate1.asset, tab_rate1.date.desc],
where=where_asset, where=where_asset)
)
days_diff = days + 5 days_diff = days + 5
query = tab_today.join( query = tab_today.join(
@ -491,8 +495,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
(tab_today.rate * 100.0 / NullIf(tab_rate2.rate, 0.00) - (tab_today.rate * 100.0 / NullIf(tab_rate2.rate, 0.00) -
100.0).as_('percent'), 100.0).as_('percent'),
distinct_on=[tab_today.id], distinct_on=[tab_today.id],
order_by=[tab_today.id, tab_rate2.date.desc] order_by=[tab_today.id, tab_rate2.date.desc])
)
return query return query
@staticmethod @staticmethod
@ -505,8 +508,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_asset.select( query = tab_asset.select(
tab_asset.percent, tab_asset.percent,
where=tab_asset.id == table.id, where=tab_asset.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -519,8 +521,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_asset.select( query = tab_asset.select(
tab_asset.percent, tab_asset.percent,
where=tab_asset.id == table.id, where=tab_asset.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -533,8 +534,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_asset.select( query = tab_asset.select(
tab_asset.percent, tab_asset.percent,
where=tab_asset.id == table.id, where=tab_asset.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -547,8 +547,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_asset.select( query = tab_asset.select(
tab_asset.percent, tab_asset.percent,
where=tab_asset.id == table.id, where=tab_asset.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -561,8 +560,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_asset.select( query = tab_asset.select(
tab_asset.percent, tab_asset.percent,
where=tab_asset.id == table.id, where=tab_asset.id == table.id)
)
return [query] return [query]
@classmethod @classmethod
@ -604,14 +602,13 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
'change_month6': 180, 'change_month6': 180,
'change_month12': 365, 'change_month12': 365,
}[x], }[x],
asset_ids=asset_id_lst, asset_ids=asset_id_lst)
)
cursor.execute(*tab_percent) cursor.execute(*tab_percent)
records = cursor.fetchall() records = cursor.fetchall()
for record in records: for record in records:
result[x][record[0]] = record[3].quantize(exp) \ result[x][record[0]] = record[3].quantize(exp) \
if record[3] is not None else None if record[3] is not None else None
return result return result
@classmethod @classmethod
@ -645,8 +642,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_asset.updtdays, tab_asset.updtdays,
tab_asset.updttime, tab_asset.updttime,
distinct_on=[tab_asset.id], distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate.date.desc], order_by=[tab_asset.id, tab_rate.date.desc])
)
query = tab_date.select( query = tab_date.select(
tab_date.id, tab_date.id,
@ -658,8 +654,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
(Extract('dow', tab_date.date) == 6), (Extract('dow', tab_date.date) == 6),
tab_date.date + Literal(2)), tab_date.date + Literal(2)),
else_=tab_date.date, else_=tab_date.date,
) + tab_date.updttime).as_('updttime'), ) + tab_date.updttime).as_('updttime'))
)
return query return query
@classmethod @classmethod
@ -673,8 +668,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_updt.select( query = tab_updt.select(
tab_updt.id, tab_updt.id,
tab_updt.updttime, tab_updt.updttime,
where=tab_updt.id.in_([x.id for x in assets]), where=tab_updt.id.in_([x.id for x in assets]))
)
cursor.execute(*query) cursor.execute(*query)
records = cursor.fetchall() records = cursor.fetchall()
@ -698,8 +692,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_updt.select( query = tab_updt.select(
tab_updt.id, tab_updt.id,
where=Operator(tab_updt.updttime, clause[2]), where=Operator(tab_updt.updttime, clause[2]))
)
return [('id', 'in', query)] return [('id', 'in', query)]
@classmethod @classmethod
@ -736,8 +729,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_asset.id, tab_asset.id,
tab_wkn.code.as_('wkn'), tab_wkn.code.as_('wkn'),
tab_secsymb.code.as_('secsymb'), tab_secsymb.code.as_('secsymb'),
tab_isin.code.as_('isin'), tab_isin.code.as_('isin'))
)
return query return query
@staticmethod @staticmethod
@ -761,8 +753,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
condition=tab_templ.id == tab_prod.template condition=tab_templ.id == tab_prod.template
).select( ).select(
tab_templ.name, tab_templ.name,
where=tab_asset.id == table.id where=tab_asset.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -775,8 +766,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_ids.select( query = tab_ids.select(
getattr(tab_ids, 'wkn'), getattr(tab_ids, 'wkn'),
where=tab_ids.id == table.id, where=tab_ids.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -789,8 +779,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_ids.select( query = tab_ids.select(
getattr(tab_ids, 'isin'), getattr(tab_ids, 'isin'),
where=tab_ids.id == table.id, where=tab_ids.id == table.id)
)
return [query] return [query]
@staticmethod @staticmethod
@ -803,8 +792,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_ids.select( query = tab_ids.select(
getattr(tab_ids, 'secsymb'), getattr(tab_ids, 'secsymb'),
where=tab_ids.id == table.id, where=tab_ids.id == table.id)
)
return [query] return [query]
@classmethod @classmethod
@ -824,8 +812,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
).select( ).select(
tab_asset.id, tab_asset.id,
where=Operator(field_qu, clause[2]) & where=Operator(field_qu, clause[2]) &
(field_qu != DEF_NONE), (field_qu != DEF_NONE))
)
return [('id', 'in', query)] return [('id', 'in', query)]
@ -898,7 +885,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
if OnlineSource.update_rate(asset): if OnlineSource.update_rate(asset):
to_run_activities.append(asset) to_run_activities.append(asset)
if len(to_run_activities) > 0: if to_run_activities:
cls.after_update_actions(to_run_activities) cls.after_update_actions(to_run_activities)
# end Asset # end Asset
@ -909,11 +896,11 @@ class AssetSourceRel(ModelSQL):
__name__ = 'investment.asset_source_rel' __name__ = 'investment.asset_source_rel'
source = fields.Many2One( source = fields.Many2One(
string='Online Source', select=True, string='Online Source',
required=True, model_name='investment.source', required=True, model_name='investment.source',
ondelete='CASCADE') ondelete='CASCADE')
asset = fields.Many2One( asset = fields.Many2One(
string='Asset', select=True, string='Asset',
required=True, model_name='investment.asset', required=True, model_name='investment.asset',
ondelete='CASCADE') ondelete='CASCADE')

View file

@ -95,7 +95,7 @@ class ChartPoint(metaclass=PoolMeta):
""" """
Rate = Pool().get('investment.rate') Rate = Pool().get('investment.rate')
if keyname is None: if not keyname:
return None return None
# check if query is for us # check if query is for us

View file

@ -84,14 +84,14 @@ class ImportWizard(Wizard):
pool = Pool() pool = Pool()
ImportWiz = pool.get('investment.imp_wiz', type='wizard') ImportWiz = pool.get('investment.imp_wiz', type='wizard')
if self.start.file_ is not None: if self.start.file_:
(lines, max_date, min_date) = ImportWiz.read_csv_file( (lines, max_date, min_date) = ImportWiz.read_csv_file(
self.start.file_.decode('utf8'), self.start.file_.decode('utf8'),
dec_divider=self.start.dec_divider, dec_divider=self.start.dec_divider,
date_fmt=self.start.date_fmt, date_fmt=self.start.date_fmt,
delimiter=self.start.field_delimiter) delimiter=self.start.field_delimiter)
if len(lines) > 0: if lines:
ImportWiz.upload_rates( ImportWiz.upload_rates(
self.start.asset, self.start.asset,
lines, min_date, max_date) lines, min_date, max_date)

View file

@ -50,6 +50,10 @@ msgctxt "model:ir.message,text:msg_missing_url"
msgid "URL for the online source '%(oname)s' is missing." msgid "URL for the online source '%(oname)s' is missing."
msgstr "URL für die Onlinequelle '%(oname)s' fehlt." msgstr "URL für die Onlinequelle '%(oname)s' fehlt."
msgctxt "model:ir.message,text:msg_bug_in_regexquery"
msgid "Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]"
msgstr "Fehler in Regex-Code des Feldes '%(fname)s': %(errmsg)s [%(code)s]"
############## ##############
# ir.ui.menu # # ir.ui.menu #

View file

@ -38,6 +38,10 @@ msgctxt "model:ir.message,text:msg_missing_url"
msgid "URL for the online source '%(oname)s' is missing." msgid "URL for the online source '%(oname)s' is missing."
msgstr "URL for the online source '%(oname)s' is missing." msgstr "URL for the online source '%(oname)s' is missing."
msgctxt "model:ir.message,text:msg_bug_in_regexquery"
msgid "Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]"
msgstr "Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]"
msgctxt "model:ir.ui.menu,name:menu_investment" msgctxt "model:ir.ui.menu,name:menu_investment"
msgid "Investment" msgid "Investment"
msgstr "Investment" msgstr "Investment"
@ -294,6 +298,18 @@ msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Sun" msgid "Mon - Sun"
msgstr "Mon - Sun" msgstr "Mon - Sun"
msgctxt "field:investment.asset,updturl:"
msgid "URL"
msgstr "URL"
msgctxt "help:investment.asset,updturl:"
msgid "URL for data retrieval."
msgstr "URL for data retrieval."
msgctxt "field:investment.asset,updturl_enable:"
msgid "URL required"
msgstr "URL required"
msgctxt "model:investment.asset_source_rel,name:" msgctxt "model:investment.asset_source_rel,name:"
msgid "Asset Source Relation" msgid "Asset Source Relation"
msgstr "Asset Source Relation" msgstr "Asset Source Relation"

View file

@ -23,6 +23,9 @@ full copyright notices and license terms. -->
<record model="ir.message" id="msg_missing_url"> <record model="ir.message" id="msg_missing_url">
<field name="text">URL for the online source '%(oname)s' is missing.</field> <field name="text">URL for the online source '%(oname)s' is missing.</field>
</record> </record>
<record model="ir.message" id="msg_bug_in_regexquery">
<field name="text">Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]</field>
</record>
</data> </data>
</tryton> </tryton>

View file

@ -63,16 +63,14 @@ class OnlineSource(ModelSQL, ModelView):
url = fields.Char(string='URL', states=STATES_WEB, depends=DEPENDS_WEB) url = fields.Char(string='URL', states=STATES_WEB, depends=DEPENDS_WEB)
fixed_url = fields.Boolean( fixed_url = fields.Boolean(
string='Fixed URL', string='Fixed URL',
states={ states={'invisible': Eval('query_method', '') != 'web'},
'invisible': Eval('query_method', '') != 'web', depends=DEPENDS_WEB,
}, depends=DEPENDS_WEB,
help='URL must be defined at investment record.') help='URL must be defined at investment record.')
nohtml = fields.Boolean( nohtml = fields.Boolean(
string='Remove HTML', string='Remove HTML',
help='Removes HTML tags before the text is interpreted.', help='Removes HTML tags before the text is interpreted.',
states={ states={'invisible': STATES_WEB['invisible']},
'invisible': STATES_WEB['invisible'], depends=DEPENDS_WEB)
}, depends=DEPENDS_WEB)
rgxdate = fields.Char( rgxdate = fields.Char(
string='Date', string='Date',
help='Regex code to find the date in the downloaded HTML file.', help='Regex code to find the date in the downloaded HTML file.',
@ -92,9 +90,8 @@ class OnlineSource(ModelSQL, ModelView):
rgxident = fields.Char( rgxident = fields.Char(
string='Identifier', string='Identifier',
help='Regex code to find the identifier in the downloaded HTML file.', help='Regex code to find the identifier in the downloaded HTML file.',
states={ states={'invisible': STATES_WEB['invisible']},
'invisible': STATES_WEB['invisible'], depends=DEPENDS_WEB)
}, depends=DEPENDS_WEB)
rgxidtype = fields.Selection( rgxidtype = fields.Selection(
string='ID-Type', selection=sel_rgxidtype, string='ID-Type', selection=sel_rgxidtype,
help='Type of identifier used to validate the result.', help='Type of identifier used to validate the result.',
@ -243,16 +240,13 @@ class OnlineSource(ModelSQL, ModelView):
isin=self.isin, isin=self.isin,
nsin=self.nsin, nsin=self.nsin,
symbol=self.symbol, symbol=self.symbol,
url=self.url, url=self.url)
)
@classmethod @classmethod
def get_query_methods(cls): def get_query_methods(cls):
""" get list of query-methods """ get list of query-methods
""" """
return [ return [('web', gettext('investment.msg_querytype_web'))]
('web', gettext('investment.msg_querytype_web')),
]
@classmethod @classmethod
def set_test_value(cls, record, name, value): def set_test_value(cls, record, name, value):
@ -260,6 +254,15 @@ class OnlineSource(ModelSQL, ModelView):
""" """
pass pass
@classmethod
def validate(cls, records):
""" check regex-code
"""
for record in records:
for x in ['rgxdate', 'rgxrate', 'rgxident']:
if x:
record.get_regex_result('', x)
@classmethod @classmethod
def run_query_method(cls, osource, isin, nsin, symbol, url, debug=False): def run_query_method(cls, osource, isin, nsin, symbol, url, debug=False):
""" run selected query to retrive data """ run selected query to retrive data
@ -280,8 +283,7 @@ class OnlineSource(ModelSQL, ModelView):
nsin=nsin, nsin=nsin,
symbol=symbol, symbol=symbol,
debug=debug, debug=debug,
url=url, url=url)
)
def call_online_source(self): def call_online_source(self):
""" use updated values to call online-source, """ use updated values to call online-source,
@ -292,7 +294,7 @@ class OnlineSource(ModelSQL, ModelView):
result = OSourc.run_query_method( result = OSourc.run_query_method(
self, self.isin, self.nsin, self.url, self, self.isin, self.nsin, self.url,
self.symbol, debug=True) self.symbol, debug=True)
if result is not None: if result:
self.text = result.get('text', None) self.text = result.get('text', None)
self.http_state = result.get('http_state', None) self.http_state = result.get('http_state', None)
self.fnddate = result.get('date', None) self.fnddate = result.get('date', None)
@ -304,19 +306,17 @@ class OnlineSource(ModelSQL, ModelView):
""" generate url """ generate url
""" """
if self.fixed_url is True: if self.fixed_url is True:
if url is None: if not url:
raise UserError(gettext( raise UserError(gettext(
'investment.msg_missing_url', 'investment.msg_missing_url',
oname=self.rec_name, oname=self.rec_name))
))
return url return url
else:
if self.url: if self.url:
return Template(self.url).substitute({ return Template(self.url).substitute({
'isin': isin if isin is not None else '', 'isin': isin if isin is not None else '',
'nsin': nsin if nsin is not None else '', 'nsin': nsin if nsin is not None else '',
'symbol': symbol if symbol is not None else '', 'symbol': symbol if symbol is not None else ''})
})
@classmethod @classmethod
def update_rate(cls, asset): def update_rate(cls, asset):
@ -335,8 +335,7 @@ class OnlineSource(ModelSQL, ModelView):
isin=asset.isin, isin=asset.isin,
nsin=asset.wkn, nsin=asset.wkn,
symbol=asset.secsymb, symbol=asset.secsymb,
url=asset.updturl, url=asset.updturl)
)
if len(updtsource.rgxident or '') > 0: if len(updtsource.rgxident or '') > 0:
# check result - same code? # check result - same code?
@ -353,15 +352,13 @@ class OnlineSource(ModelSQL, ModelView):
'update_rate: got wrong code ' + 'update_rate: got wrong code ' +
'"%(wrong)s" - expected "%(exp)s"' % { '"%(wrong)s" - expected "%(exp)s"' % {
'exp': asset_code, 'exp': asset_code,
'wrong': code, 'wrong': code})
})
continue continue
to_create = { to_create = {
'date': rate_data.get('date', None), 'date': rate_data.get('date', None),
'rate': rate_data.get('rate', None), 'rate': rate_data.get('rate', None),
'asset': asset.id, 'asset': asset.id}
}
if (to_create['date'] is not None) and \ if (to_create['date'] is not None) and \
(to_create['rate'] is not None): (to_create['rate'] is not None):
# check if exists # check if exists
@ -380,13 +377,22 @@ class OnlineSource(ModelSQL, ModelView):
def get_regex_result(self, html_text, field_name): def get_regex_result(self, html_text, field_name):
""" run regex on html-text, convert result """ run regex on html-text, convert result
""" """
OSource = Pool().get('investment.source')
rgxcode = getattr(self, field_name) or '' rgxcode = getattr(self, field_name) or ''
if len(rgxcode) == 0: if len(rgxcode) == 0:
return None return None
search_result = re.compile(rgxcode).search(html_text) try:
if search_result is None: search_result = re.compile(rgxcode).search(html_text)
return None if search_result is None:
return None
except Exception as e1:
raise UserError(gettext(
'investment.msg_bug_in_regexquery',
errmsg=str(e1),
fname=getattr(OSource, field_name).string,
code=rgxcode))
try: try:
result = search_result.group(1) result = search_result.group(1)
@ -427,8 +433,7 @@ class OnlineSource(ModelSQL, ModelView):
isin=isin, isin=isin,
nsin=nsin, nsin=nsin,
symbol=symbol, symbol=symbol,
url=url, url=url),
),
allow_redirects=True, allow_redirects=True,
timeout=5.0) timeout=5.0)
@ -437,7 +442,7 @@ class OnlineSource(ModelSQL, ModelView):
'msg': res1.reason, 'msg': res1.reason,
} }
if res1.status_code in [200, 204]: if res1.status_code in [200, 204, 410]:
html = res1.text html = res1.text
# remove html-tags # remove html-tags

23
rate.py
View file

@ -4,7 +4,7 @@
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.model import ( from trytond.model import (
ModelView, ModelSQL, fields, Unique, Check, SymbolMixin) ModelView, ModelSQL, fields, Unique, Check, SymbolMixin, Index)
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval from trytond.pyson import Eval
@ -15,11 +15,11 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
__name__ = 'investment.rate' __name__ = 'investment.rate'
asset = fields.Many2One( asset = fields.Many2One(
string='Asset', required=True, select=True, ondelete='CASCADE', string='Asset', required=True, ondelete='CASCADE',
model_name='investment.asset') model_name='investment.asset')
date = fields.Date(string='Date', required=True, select=True) date = fields.Date(string='Date', required=True)
rate = fields.Numeric( rate = fields.Numeric(
string='Rate', required=True, select=True, string='Rate', required=True,
digits=(16, Eval('asset_digits', 4)), depends=['asset_digits']) digits=(16, Eval('asset_digits', 4)), depends=['asset_digits'])
asset_digits = fields.Function(fields.Integer( asset_digits = fields.Function(fields.Integer(
@ -46,6 +46,21 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
'currency.msg_rate_positive'), 'currency.msg_rate_positive'),
] ]
cls._order.insert(0, ('date', 'DESC')) cls._order.insert(0, ('date', 'DESC'))
cls._sql_indexes.update({
Index(
t,
(t.date, Index.Range(order='DESC'))),
Index(
t,
(t.rate, Index.Range())),
Index(
t,
(t.asset, Index.Equality())),
Index(
t,
(t.asset, Index.Equality()),
(t.date, Index.Range(order='DESC'))),
})
@classmethod @classmethod
def default_date(cls): def default_date(cls):

View file

@ -39,7 +39,7 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]} modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]}
# tryton-version # tryton-version
major_version = 6 major_version = 7
minor_version = 0 minor_version = 0
requires = ['requests>=2.26', 'html2text'] requires = ['requests>=2.26', 'html2text']
@ -91,6 +91,7 @@ setup(
'License :: OSI Approved :: GNU General Public License (GPL)', 'License :: OSI Approved :: GNU General Public License (GPL)',
'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
], ],
keywords='tryton investment shares commodities', keywords='tryton investment shares commodities',

View file

@ -71,9 +71,9 @@ full copyright notices and license terms. -->
<field name="name">www.sbroker.de</field> <field name="name">www.sbroker.de</field>
<field name="url">https://www.sbroker.de/sbl/mdaten_analyse/dksuche_a?SEARCH_VALUE=${isin}</field> <field name="url">https://www.sbroker.de/sbl/mdaten_analyse/dksuche_a?SEARCH_VALUE=${isin}</field>
<field name="nohtml" eval="True"/> <field name="nohtml" eval="True"/>
<field name="rgxdate">\nDatum / Uhrzeit: (\d+\.\d+\.\d+) / \d+:\d+\s+\n</field> <field name="rgxdate">\nDatum\s*(?:\/ Uhrzeit)*: (\d+\.\d+\.\d+)\s*(?:\/ \d+:\d+)*\s*\n</field>
<field name="rgxdatefmt">%d.%m.%y</field> <field name="rgxdatefmt">%d.%m.%y</field>
<field name="rgxrate">Kurs aktuell .* (\d+,\d+)\s+EUR.*\n</field> <field name="rgxrate">(?:Kurs aktuell|Rucknahmepreis)\s+[()\w\./\[\]!]+\s+(\d+,\d+)\s+(EUR|€)</field>
<field name="rgxdecimal">,</field> <field name="rgxdecimal">,</field>
<field name="rgxident">\nWKN / ISIN: [A-Z,0-9]+ / ([A-Z,0-9]+)\s+\n</field> <field name="rgxident">\nWKN / ISIN: [A-Z,0-9]+ / ([A-Z,0-9]+)\s+\n</field>
<field name="rgxidtype">isin</field> <field name="rgxidtype">isin</field>

View file

@ -1,17 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of # This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms. # this repository contains the full copyright notices and license terms.
import trytond.tests.test_tryton
import unittest
from .test_module import InvestmentTestCase
__all__ = ['suite']
def suite():
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
InvestmentTestCase))
return suite

View file

@ -6,6 +6,7 @@
from trytond.tests.test_tryton import with_transaction from trytond.tests.test_tryton import with_transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.exceptions import UserError
from decimal import Decimal from decimal import Decimal
from datetime import time, date, datetime from datetime import time, date, datetime
from unittest.mock import MagicMock from unittest.mock import MagicMock
@ -131,5 +132,54 @@ High 34,87 EUR
'rgxdate' 'rgxdate'
), date(2022, 3, 14)) ), date(2022, 3, 14))
@with_transaction()
def test_waitlist_source_check_regex_validate(self):
""" create source, check validation of regex-code
"""
pool = Pool()
OSource = pool.get('investment.source')
self.assertRaisesRegex(
UserError,
r"Error in regex code of field 'Date': nothing to repeat " +
r"at position 0 \[\*+ multiple repeat\]",
OSource.create,
[{
'name': 'Check date',
'rgxdate': '** multiple repeat',
'rgxrate': 'rate -- multiple repeat',
'rgxident': 'identifiert ** multiple repeat',
}])
self.assertRaisesRegex(
UserError,
r"Error in regex code of field 'Rate': multiple repeat " +
r"at position 6 \[rate \*+ multiple repeat\]",
OSource.create,
[{
'name': 'Check rate',
'rgxdate': '-- multiple repeat',
'rgxrate': 'rate ** multiple repeat',
'rgxident': 'identifiert -- multiple repeat',
}])
self.assertRaisesRegex(
UserError,
r"Error in regex code of field 'Identifier': multiple " +
r"repeat at position 13 \[identifiert \*+ multiple repeat\]",
OSource.create,
[{
'name': 'Check rgxident',
'rgxdate': '-- multiple repeat',
'rgxrate': 'rate -- multiple repeat',
'rgxident': 'identifiert ** multiple repeat',
}])
OSource.create([{
'name': 'Check rgxident',
'rgxdate': '-- multiple repeat',
'rgxrate': 'rate -- multiple repeat',
'rgxident': 'identifiert -- multiple repeat',
}])
# end SourceTestCase # end SourceTestCase

View file

@ -1,5 +1,5 @@
[tryton] [tryton]
version=6.0.25 version=7.0.0
depends: depends:
ir ir
res res

View file

@ -29,7 +29,7 @@ class UpdateSoureWizard(Wizard):
if OnlineSource.update_rate(asset): if OnlineSource.update_rate(asset):
to_run_activities.append(asset) to_run_activities.append(asset)
if len(to_run_activities) > 0: if to_run_activities:
Asset.after_update_actions(to_run_activities) Asset.after_update_actions(to_run_activities)
return 'end' return 'end'

View file

@ -1 +1 @@
diagram;6.0.7;6.0.999;mds

View file

@ -4,12 +4,12 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<tree> <tree>
<field name="name" expand="1"/> <field name="name" expand="1"/>
<field name="change_day1"/> <field name="change_day1" optional="0"/>
<field name="change_month1"/> <field name="change_month1" optional="0"/>
<field name="change_month3"/> <field name="change_month3" optional="0"/>
<field name="change_month6"/> <field name="change_month6" optional="0"/>
<field name="date"/> <field name="date" optional="0"/>
<field name="rate" symbol="asset_symbol"/> <field name="rate" symbol="asset_symbol" optional="0"/>
<field name="isin"/> <field name="isin" optional="0"/>
<field name="wkn" /> <field name="wkn" optional="0"/>
</tree> </tree>