Skip to content

Commit 0823d9c

Browse files
authored
Merge pull request #124 from finmars-platform/new-ttype-input
New ttype input
2 parents 1a6a894 + 2afae7a commit 0823d9c

File tree

14 files changed

+176
-22
lines changed

14 files changed

+176
-22
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,4 +168,6 @@ postgres_data/
168168
rabbitmq_data/
169169
redis_data/
170170

171+
finmars-core.iml
172+
171173
*.sql

poms/clients/fields.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from poms.clients.models import Client
2+
from poms.common.fields import PrimaryKeyRelatedFilteredField
3+
4+
5+
class ClientField(PrimaryKeyRelatedFilteredField):
6+
queryset = Client.objects

poms/clients/serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
)
1717

1818

19-
class ClientsSerializer(ModelWithUserCodeSerializer):
19+
class ClientSerializer(ModelWithUserCodeSerializer):
2020
master_user = MasterUserField()
2121

2222
portfolios = serializers.PrimaryKeyRelatedField(queryset=Portfolio.objects.all(), many=True, required=False)
@@ -180,7 +180,7 @@ def __init__(self, *args, **kwargs):
180180
super().__init__(*args, **kwargs)
181181
self.fields["user_code"] = UserCodeField()
182182
self.fields["deleted_user_code"] = UserCodeField(read_only=True)
183-
self.fields["client_object"] = ClientsSerializer(source="client", many=False, read_only=True)
183+
self.fields["client_object"] = ClientSerializer(source="client", many=False, read_only=True)
184184

185185
def create(self, validated_data):
186186
instance = super().create(validated_data)

poms/clients/views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from poms.clients.filters import ClientSecretFilterSet, ClientsFilterSet
44
from poms.clients.models import Client, ClientSecret
5-
from poms.clients.serializers import ClientSecretSerializer, ClientsSerializer
5+
from poms.clients.serializers import ClientSecretSerializer, ClientSerializer
66
from poms.common.views import AbstractModelViewSet
77
from poms.users.filters import OwnerByMasterUserFilter
88

@@ -15,7 +15,7 @@ class ClientsViewSet(AbstractModelViewSet):
1515
OwnerByMasterUserFilter,
1616
]
1717
filterset_class = ClientsFilterSet
18-
serializer_class = ClientsSerializer
18+
serializer_class = ClientSerializer
1919
ordering_fields = [
2020
"user_code",
2121
"name",
@@ -27,7 +27,7 @@ class ClientsViewSet(AbstractModelViewSet):
2727
detail=False,
2828
methods=["get"],
2929
url_path="light",
30-
serializer_class=ClientsSerializer,
30+
serializer_class=ClientSerializer,
3131
)
3232
def list_light(self, request, *args, **kwargs):
3333
queryset = self.filter_queryset(self.get_queryset())

poms/common/authentication.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def authenticate_credentials(self, key, request=None):
111111
# raise exceptions.AuthenticationFailed(msg)
112112
try:
113113
userinfo = self.keycloak.userinfo(key)
114-
except Exception:
114+
except Exception as e:
115115
msg = _("Invalid or expired token.")
116116
raise exceptions.AuthenticationFailed(msg) from e # noqa: F821
117117

poms/expressions_engine/formula.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,8 @@ def _get_supported_models_serializer_class():
761761
AccountEvalSerializer,
762762
AccountTypeEvalSerializer,
763763
)
764+
from poms.clients.models import Client
765+
from poms.clients.serializers import ClientSerializer
764766
from poms.counterparties.models import Counterparty, Responsible
765767
from poms.counterparties.serializers import (
766768
CounterpartyEvalSerializer,
@@ -807,6 +809,8 @@ def _get_supported_models_serializer_class():
807809
)
808810
from poms.users.models import Member
809811
from poms.users.serializers import MemberSerializer
812+
from poms.vault.models import VaultRecord
813+
from poms.vault.serializers import VaultRecordSerializer
810814

811815
return {
812816
Account: AccountEvalSerializer,
@@ -833,6 +837,8 @@ def _get_supported_models_serializer_class():
833837
Member: MemberSerializer,
834838
Country: CountrySerializer,
835839
AccrualCalculationSchedule: AccrualCalculationScheduleSerializer,
840+
Client: ClientSerializer,
841+
VaultRecord: VaultRecordSerializer,
836842
}
837843

838844

poms/portfolios/serializers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ def get_first_transaction(self, instance: Portfolio) -> dict:
223223

224224
def __init__(self, *args, **kwargs):
225225
from poms.accounts.serializers import AccountViewSerializer
226-
from poms.clients.serializers import ClientsSerializer
226+
from poms.clients.serializers import ClientSerializer
227227
from poms.counterparties.serializers import (
228228
CounterpartyViewSerializer,
229229
ResponsibleViewSerializer,
@@ -242,7 +242,7 @@ def __init__(self, *args, **kwargs):
242242
self.fields["transaction_types_object"] = TransactionTypeViewSerializer(
243243
source="transaction_types", many=True, read_only=True
244244
)
245-
self.fields["client_object"] = ClientsSerializer(source="client", many=False, read_only=True)
245+
self.fields["client_object"] = ClientSerializer(source="client", many=False, read_only=True)
246246

247247
def create_register_if_not_exists(
248248
self, instance, register_currency=None, register_pricing_policy=None, register_instrument_type=None

poms/transactions/fields.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import json
2+
import logging
3+
14
from django.contrib.contenttypes.models import ContentType
25
from django.core.exceptions import ObjectDoesNotExist
36
from django.utils.encoding import force_str
47
from django.utils.translation import gettext_lazy as _
8+
from rest_framework import serializers
59
from rest_framework.fields import ReadOnlyField
610
from rest_framework.relations import RelatedField
711

@@ -17,6 +21,8 @@
1721
)
1822
from poms.users.filters import OwnerByMasterUserFilter
1923

24+
_l = logging.getLogger("poms.transactions")
25+
2026

2127
class TransactionTypeGroupField(UserCodeOrPrimaryKeyRelatedField):
2228
queryset = TransactionTypeGroup.objects
@@ -112,3 +118,22 @@ def __init__(self, **kwargs):
112118

113119
def to_representation(self, obj):
114120
return f"{obj.app_label}.{obj.model}"
121+
122+
123+
class CharOrJSONField(serializers.CharField):
124+
def to_representation(self, value):
125+
# Try to parse string to JSON
126+
if isinstance(value, str):
127+
try:
128+
val = json.loads(value)
129+
# _l.info("CharOrJSONField to_representation %s" % val)
130+
return val
131+
except json.JSONDecodeError:
132+
pass
133+
return value
134+
135+
def to_internal_value(self, data):
136+
# Accept dicts and convert to JSON string
137+
if isinstance(data, dict):
138+
return json.dumps(data)
139+
return super().to_internal_value(data)

poms/transactions/handlers.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from rest_framework.exceptions import ValidationError
1515

1616
from poms.accounts.models import Account
17+
from poms.clients.models import Client
1718
from poms.common.utils import date_now, format_float, format_float_to_2
1819
from poms.counterparties.models import Counterparty, Responsible
1920
from poms.currencies.models import Currency
@@ -46,8 +47,9 @@
4647
TransactionType,
4748
TransactionTypeInput,
4849
)
49-
from poms.transactions.utils import generate_user_fields
50+
from poms.transactions.utils import _read_json_text, generate_user_fields
5051
from poms.users.models import EcosystemDefault
52+
from poms.vault.models import VaultRecord
5153

5254
_l = logging.getLogger("poms.transactions")
5355

@@ -164,6 +166,9 @@ def __init__( # noqa: PLR0913, PLR0915
164166
if complex_transaction and not complex_transaction_status:
165167
self.complex_transaction_status = complex_transaction.status_id
166168

169+
if self.clear_execution_log:
170+
self.complex_transaction.execution_log = ""
171+
167172
self._context = context
168173
self._context["transaction_type"] = self.transaction_type
169174
self._id_seq = 0
@@ -178,9 +183,6 @@ def __init__( # noqa: PLR0913, PLR0915
178183
for i in range(10):
179184
self.values[f"phantom_instrument_{i}"] = None
180185

181-
if self.clear_execution_log:
182-
self.complex_transaction.execution_log = ""
183-
184186
self.complex_transaction.owner = self.member
185187
# self.complex_transaction.save() # it will create empty transaction in db!
186188

@@ -267,6 +269,10 @@ def _get_val_by_model_cls_for_transaction_type_input(master_user, value, model_c
267269
return EventClass.objects.get(user_code=value)
268270
elif issubclass(model_class, NotificationClass):
269271
return NotificationClass.objects.get(user_code=value)
272+
elif issubclass(model_class, Client):
273+
return Client.objects.get(user_code=value)
274+
elif issubclass(model_class, VaultRecord):
275+
return VaultRecord.objects.get(user_code=value)
270276
except Exception:
271277
_l.debug(f"Could not find default value relation {value}")
272278
return None
@@ -305,6 +311,10 @@ def _get_val_by_model_cls_for_complex_transaction_input(master_user, obj, model_
305311
return EventClass.objects.get(user_code=obj.value_relation)
306312
elif issubclass(model_class, NotificationClass):
307313
return NotificationClass.objects.get(user_code=obj.value_relation)
314+
elif issubclass(model_class, Client):
315+
return Client.objects.get(user_code=obj.value_relation)
316+
elif issubclass(model_class, VaultRecord):
317+
return VaultRecord.objects.get(user_code=obj.value_relation)
308318
except Exception:
309319
_l.error(f"Could not find default value relation {obj.value_relation} ")
310320
return None
@@ -340,6 +350,9 @@ def _get_val_by_model_cls_for_complex_transaction_input(master_user, obj, model_
340350
TransactionTypeInput.SELECTOR,
341351
):
342352
value = ci.value_string
353+
elif i.value_type == TransactionTypeInput.JSON:
354+
value = _read_json_text(ci.value_string)
355+
_l.info("JSON read: %r type=%s", value, type(value).__name__ if value is not None else None)
343356
elif i.value_type == TransactionTypeInput.NUMBER:
344357
value = ci.value_float
345358
elif i.value_type == TransactionTypeInput.DATE:
@@ -353,7 +366,7 @@ def _get_val_by_model_cls_for_complex_transaction_input(master_user, obj, model_
353366
if value is not None:
354367
self.values[i.name] = value
355368

356-
# _l.debug('self.inputs %s' % self.inputs)
369+
# _l.debug('before self.values %s' % self.values)
357370

358371
self.record_execution_progress("==== COMPLEX TRANSACTION VALUES ====", self.values)
359372

@@ -416,6 +429,8 @@ def _get_val_by_model_cls_for_complex_transaction_input(master_user, obj, model_
416429

417430
self.record_execution_progress("==== CALCULATED INPUTS ====")
418431

432+
# _l.info("check values %s" % self.values)
433+
419434
for key, value in self.values.items():
420435
self.record_execution_progress(f"Key: {key}. Value: {value}. Type: {type(self.values[key]).__name__}")
421436

@@ -2215,13 +2230,26 @@ def _save_inputs(self):
22152230
ci.complex_transaction = self.complex_transaction
22162231
ci.transaction_type_input = ti
22172232

2218-
if ti.value_type in (
2219-
TransactionTypeInput.STRING,
2220-
TransactionTypeInput.SELECTOR,
2221-
):
2233+
if ti.value_type in (TransactionTypeInput.STRING, TransactionTypeInput.SELECTOR):
22222234
if val is None:
22232235
val = ""
22242236
ci.value_string = val
2237+
elif ti.value_type == TransactionTypeInput.JSON:
2238+
# val may be dict/list OR already a JSON string
2239+
if val is None:
2240+
ci.value_string = ""
2241+
elif isinstance(val, dict):
2242+
ci.value_string = json.dumps(val, ensure_ascii=False, separators=(",", ":"))
2243+
elif isinstance(val, str):
2244+
# keep if it is valid JSON; else wrap it as JSON string
2245+
try:
2246+
json.loads(val)
2247+
ci.value_string = val
2248+
except json.JSONDecodeError:
2249+
ci.value_string = json.dumps(val, ensure_ascii=False)
2250+
else:
2251+
# numbers, bools, etc. -> store valid JSON
2252+
ci.value_string = json.dumps(val, ensure_ascii=False)
22252253
elif ti.value_type == TransactionTypeInput.NUMBER:
22262254
if val is None:
22272255
val = 0.0
@@ -2249,9 +2277,21 @@ def execute_user_fields_expressions(self):
22492277
"transactions": trns,
22502278
}
22512279

2280+
# do not remove, somehow json inputs are always strings, which leads to impossiblity to address by keys
2281+
for i in self.inputs:
2282+
if i.value_type == TransactionTypeInput.JSON:
2283+
val = self.values.get(i.name)
2284+
if isinstance(val, str):
2285+
try:
2286+
self.values[i.name] = json.loads(val)
2287+
except json.JSONDecodeError:
2288+
_l.error("execute_user_fields_expressions decode error")
2289+
22522290
for key, value in self.values.items():
22532291
names[key] = value
22542292

2293+
# _l.info('before names %s' % names)
2294+
22552295
self.record_execution_progress("Calculating User Fields")
22562296

22572297
_result_for_log = {}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.22 on 2025-10-29 14:46
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('transactions', '0038_remove_complextransaction_created_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='transactiontypeinput',
15+
name='value_type',
16+
field=models.PositiveSmallIntegerField(choices=[(20, 'Number'), (10, 'String'), (40, 'Date'), (100, 'Relation'), (110, 'Selector'), (120, 'Button'), (130, 'JSON')], default=20, verbose_name='value type'),
17+
),
18+
]

0 commit comments

Comments
 (0)