Compare commits

...

12 commits
6.8 ... main

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
16 changed files with 263 additions and 199 deletions

View file

@ -9,7 +9,7 @@ pip install mds-investment
Requires Requires
======== ========
- Tryton 6.8 - Tryton 7.0
How to How to
====== ======
@ -22,6 +22,6 @@ You can define the course sources yourself.
Changes Changes
======= =======
*6.8.24 - 07.06.2023* *7.0.0 - 01.12.2023*
- portet to Tryton 6.8 - compatibility to Tryton 7.0

227
asset.py
View file

@ -15,6 +15,7 @@ from sql.functions import CurrentDate, CurrentTimestamp, Round, Extract
from sql.conditionals import Case, Coalesce, NullIf from sql.conditionals import Case, Coalesce, NullIf
from sql import Literal from sql import Literal
from .diagram import Concat2 from .diagram import Concat2
from .const import DEF_NONE
digits_percent = 2 digits_percent = 2
@ -31,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")
@ -45,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')
@ -73,9 +71,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
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,
@ -100,8 +96,8 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
string='URL', string='URL',
help='URL for data retrieval.', help='URL for data retrieval.',
states={ states={
'invisible': Eval('updturl_enable', False) == False, 'invisible': ~Eval('updturl_enable', False),
'required': Eval('updturl_enable', False) == True, 'required': Eval('updturl_enable', False),
}, depends=['updturl_enable']) }, depends=['updturl_enable'])
updturl_enable = fields.Function(fields.Boolean( updturl_enable = fields.Function(fields.Boolean(
string='URL required', readonly=True, string='URL required', readonly=True,
@ -111,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')
@ -191,7 +186,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_asset.select( query = tab_asset.select(
tab_asset.id, tab_asset.id,
tab_asset.updtsource, tab_asset.updtsource,
where=tab_asset.updtsource != None, where=tab_asset.updtsource != DEF_NONE,
) )
cursor.execute(*query) cursor.execute(*query)
records = cursor.fetchall() records = cursor.fetchall()
@ -200,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')
@ -261,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)
@ -330,23 +325,24 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
""" """
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
(query, tab_asset) = cls.get_name_symbol_sql()
query.where = tab_asset.id.in_([x.id for x in assets])
cursor.execute(*query)
records = cursor.fetchall()
result = {x: {y.id: None for y in assets} for x in names} result = {x: {y.id: None for y in assets} for x in names}
for record in records: (query, tab_asset) = cls.get_name_symbol_sql()
values = { if assets:
'name': record[1], query.where = tab_asset.id.in_([x.id for x in assets])
'product_uom': record[2], cursor.execute(*query)
'symbol': record[3], records = cursor.fetchall()
'asset_symbol': record[0],
}
for name in names: for record in records:
result[name][record[0]] = values[name] values = {
'name': record[1],
'product_uom': record[2],
'symbol': record[3],
'asset_symbol': record[0],
}
for name in names:
result[name][record[0]] = values[name]
return result return result
@classmethod @classmethod
@ -384,32 +380,32 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
def get_rate_data(cls, assets, names): def get_rate_data(cls, assets, names):
""" get date and rate of asset """ get date and rate of asset
""" """
Asset2 = Pool().get('investment.asset')
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
(query, tab_asset) = cls.get_rate_data_sql()
query.where = tab_asset.id.in_([x.id for x in assets])
cursor.execute(*query)
records = cursor.fetchall()
result = {x: {y.id: None for y in assets} for x in names} result = {x: {y.id: None for y in assets} for x in names}
for record in records: if assets:
(id1, rate1, date1, id_rate) = record (query, tab_asset) = cls.get_rate_data_sql()
query.where = tab_asset.id.in_([x.id for x in assets])
curr_digits = {x.id: x.currency_digits for x in assets}
asset = Asset2(id1) cursor.execute(*query)
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4)) records = cursor.fetchall()
values = { for record in records:
'rate': record[1].quantize(exp), (id1, rate1, date1, id_rate) = record
'date': record[2],
'change_symbol': id_rate,
}
for name in names: curr_dig = curr_digits.get(id1, 4)
result[name][record[0]] = values[name] exp = Decimal(Decimal(1) / 10 ** curr_dig)
values = {
'rate': record[1].quantize(exp),
'date': record[2],
'change_symbol': id_rate,
}
for name in names:
result[name][record[0]] = values[name]
return result return result
@classmethod @classmethod
@ -421,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
@ -434,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
@ -447,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
@ -460,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
@ -472,8 +464,6 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
""" """
pool = Pool() pool = Pool()
Rate = pool.get('investment.rate') Rate = pool.get('investment.rate')
Asset2 = pool.get('investment.asset')
tab_asset = Asset2.__table__()
tab_rate1 = Rate.__table__() tab_rate1 = Rate.__table__()
tab_rate2 = Rate.__table__() tab_rate2 = Rate.__table__()
context = Transaction().context context = Transaction().context
@ -481,19 +471,15 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query_date = context.get('qdate', CurrentDate()) query_date = context.get('qdate', CurrentDate())
where_asset = tab_rate1.date <= query_date where_asset = tab_rate1.date <= query_date
if isinstance(asset_ids, list): if isinstance(asset_ids, list):
where_asset &= tab_asset.id.in_(asset_ids) where_asset &= tab_rate1.asset.in_(asset_ids)
tab_today = tab_asset.join( tab_today = tab_rate1.select(
tab_rate1, tab_rate1.asset.as_('id'),
condition=tab_asset.id == tab_rate1.asset,
).select(
tab_asset.id,
tab_rate1.date, tab_rate1.date,
tab_rate1.rate, tab_rate1.rate,
distinct_on=[tab_asset.id], distinct_on=[tab_rate1.asset],
order_by=[tab_asset.id, 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(
@ -509,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
@ -523,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
@ -537,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
@ -551,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
@ -565,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
@ -579,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
@ -612,22 +592,22 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
exp = Decimal(Decimal(1) / 10 ** digits_percent) exp = Decimal(Decimal(1) / 10 ** digits_percent)
asset_id_lst = [x.id for x in assets] asset_id_lst = [x.id for x in assets]
for x in names: if asset_id_lst and names:
tab_percent = cls.get_percentage_sql( for x in names:
days={ tab_percent = cls.get_percentage_sql(
'change_day1': 0, days={
'change_month1': 30, 'change_day1': 0,
'change_month3': 90, 'change_month1': 30,
'change_month6': 180, 'change_month3': 90,
'change_month12': 365, 'change_month6': 180,
}[x], 'change_month12': 365,
asset_ids=asset_id_lst, }[x],
) 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
@ -662,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,
@ -675,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
@ -690,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()
@ -715,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
@ -753,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
@ -778,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
@ -792,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
@ -806,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
@ -820,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
@ -841,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 != None), (field_qu != DEF_NONE))
)
return [('id', 'in', query)] return [('id', 'in', query)]
@ -856,20 +826,19 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
result = {x: {y.id: None for y in assets} for x in names} result = {x: {y.id: None for y in assets} for x in names}
if assets:
query = cls.get_identifier_sql(tab_asset)
query.where = tab_asset.id.in_([x.id for x in assets])
query = cls.get_identifier_sql(tab_asset) cursor.execute(*query)
query.where = tab_asset.id.in_([x.id for x in assets]) l1 = cursor.fetchall()
cursor.execute(*query) for x in l1:
l1 = cursor.fetchall() (id1, wkn, secsymb, isin) = x
r1 = {'wkn': wkn, 'secsymb': secsymb, 'isin': isin}
for x in l1:
(id1, wkn, secsymb, isin) = x
r1 = {'wkn': wkn, 'secsymb': secsymb, 'isin': isin}
for n in names:
result[n][id1] = r1[n]
for n in names:
result[n][id1] = r1[n]
return result return result
def get_rec_name(self, name): def get_rec_name(self, name):
@ -916,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

8
const.py Normal file
View file

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# This file is part of the cashbook-module from m-ds.de for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
DEF_TRUE = True
DEF_NONE = None

View file

@ -60,16 +60,16 @@ class GraphDef(metaclass=PoolMeta):
return None return None
if self.scaling == 'alldata': if self.scaling == 'alldata':
query = [('asset.id', '=', self.asset.id)] query = [('asset', '=', self.asset.id)]
elif self.scaling == 'view': elif self.scaling == 'view':
query = [ query = [
('asset.id', '=', self.asset.id), ('asset', '=', self.asset.id),
('date', '>=', self.chart.used_start_date()), ('date', '>=', self.chart.used_start_date()),
('date', '<=', self.chart.used_end_date()), ('date', '<=', self.chart.used_end_date()),
] ]
elif self.scaling == 'six': elif self.scaling == 'six':
query = [ query = [
('asset.id', '=', self.asset.id), ('asset', '=', self.asset.id),
('date', '>=', self.chart.used_start_date() - ('date', '>=', self.chart.used_start_date() -
timedelta(days=180)), timedelta(days=180)),
('date', '<=', self.chart.used_end_date()), ('date', '<=', self.chart.used_end_date()),
@ -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
@ -104,12 +104,12 @@ class ChartPoint(metaclass=PoolMeta):
before = Rate.search([ before = Rate.search([
('date', '<', query_date), ('date', '<', query_date),
('asset.id', '=', asset_id), ('asset', '=', asset_id),
], limit=1, order=[('date', 'DESC')]) ], limit=1, order=[('date', 'DESC')])
after = Rate.search([ after = Rate.search([
('date', '>', query_date), ('date', '>', query_date),
('asset.id', '=', asset_id), ('asset', '=', asset_id),
], limit=1, order=[('date', 'ASC')]) ], limit=1, order=[('date', 'ASC')])
if (len(before) == 1) and (len(after) == 1): if (len(before) == 1) and (len(after) == 1):

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)
@ -173,7 +173,8 @@ class ImportWizard(Wizard):
datefmt='dd%sdd' % dec_divider, datefmt='dd%sdd' % dec_divider,
colnr='2')) colnr='2'))
if isinstance(date_val, date) and isinstance(rate_val, Decimal): if isinstance(date_val, date) and isinstance(
rate_val, Decimal):
result.append({'date': date_val, 'rate': rate_val}) result.append({'date': date_val, 'rate': rate_val})
# date range # date range

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.',
@ -86,14 +84,14 @@ class OnlineSource(ModelSQL, ModelView):
states=STATES_WEB, depends=DEPENDS_WEB) states=STATES_WEB, depends=DEPENDS_WEB)
rgxdecimal = fields.Selection( rgxdecimal = fields.Selection(
string='Decimal Separator', string='Decimal Separator',
help='Decimal separator for converting the market value into a number.', help='Decimal separator for converting the market ' +
'value into a number.',
selection=sel_rgxdecimal, states=STATES_WEB, depends=DEPENDS_WEB) selection=sel_rgxdecimal, states=STATES_WEB, depends=DEPENDS_WEB)
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.',
@ -242,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):
@ -259,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
@ -279,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,
@ -291,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)
@ -303,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):
@ -334,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?
@ -352,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
@ -379,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)
@ -426,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)
@ -436,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

View file

@ -53,6 +53,13 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
Index( Index(
t, t,
(t.rate, Index.Range())), (t.rate, Index.Range())),
Index(
t,
(t.asset, Index.Equality())),
Index(
t,
(t.asset, Index.Equality()),
(t.date, Index.Range(order='DESC'))),
}) })
@classmethod @classmethod

View file

@ -39,8 +39,8 @@ 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 = 8 minor_version = 0
requires = ['requests>=2.26', 'html2text'] requires = ['requests>=2.26', 'html2text']
for dep in info.get('depends', []): for dep in info.get('depends', []):

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

@ -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.8.24 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

@ -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" symbol="change_symbol"/> <field name="change_day1" optional="0"/>
<field name="change_month1" symbol="change_symbol"/> <field name="change_month1" optional="0"/>
<field name="change_month3" symbol="change_symbol"/> <field name="change_month3" optional="0"/>
<field name="change_month6" symbol="change_symbol"/> <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>