• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TampereHacklab / mulysa / 13215816783

08 Feb 2025 12:54PM UTC coverage: 83.824% (+0.2%) from 83.657%
13215816783

push

github

web-flow
CI test for makemessages (#557)

* CI test for makemessages

do we want something like this to check that if there are new messages that the po files are updated for translators?

this might create quite a bit of conflicts but then again those should be really easy to fix by just running makemessages again after rebasing...

* and now the ci should be green

* change how the message is built

* diff just po files

* fix stupid typo

* makemessages to get correct line numbers after master merge

* revert the test change for ci

* better way for the translation, fstrings would be nice but are not yet supported

* fixed a stupid typo

* also check for obsolete translations

* update readme to reflect that no obosolete translations should be left

0 of 1 new or added line in 1 file covered. (0.0%)

27 existing lines in 2 files now uncovered.

2135 of 2547 relevant lines covered (83.82%)

0.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.94
/utils/dataimport.py
1
#!/usr/bin/env python
2
# -*- coding: utf-8 -*-
3
import logging
1✔
4
import datetime
1✔
5
import re
1✔
6
from decimal import Decimal
1✔
7

8
from users.models import BankTransaction
1✔
9

10
from utils.businesslogic import BusinessLogic
1✔
11
from utils.holvitoolbox import HolviToolbox
1✔
12

13
logger = logging.getLogger(__name__)
1✔
14

15

16
class ParseError(Exception):
1✔
17
    """Raised when the data has invalid value"""
18

19
    pass
1✔
20

21

22
"""
1✔
23
This class contains data importers
24

25
 - Bank events. This is in form of bank transactions in and out of an
26
   account. Only one account is supported currently.
27
   Current implementation uses Nordea TITO format which seems to be
28
   some kind of standard.
29
   Use it if you can or write your own importer.
30

31
After data is imported, businesslogic to update all user data is
32
invoked. All functions return an object showing counts of imported,
33
already existing and failed items and array of failed rows. These
34
are displayed in the import UI.
35
"""
36

37

38
class DataImport:
1✔
39
    # Nordea TITO import. TITO spec here: https://www.nordea.fi/Images/146-84478/xml_tiliote.pdf
40
    # Note: this is the text-based TITO, not XML.
41
    # DEPRECATED: but the decorator is only available after python 3.13
42
    @staticmethod
1✔
43
    def import_tito(f):
1✔
44
        tito = f.read().decode("utf8")
1✔
45
        lines = tito.split("\n")
1✔
46
        imported = exists = error = 0
1✔
47
        failedrows = []
1✔
48
        for line in lines[1:]:
1✔
49
            logger.debug(f"import_tito - Processing line: {line}")
1✔
50
            try:
1✔
51
                if len(line) == 0 or line[0] != "T":
1✔
52
                    raise ParseError("Empty line or not starting with T")
1✔
53
                data_type = int(line[1:3])
1✔
54
                # Currently handle only type 10 (basic transaction data)
55
                if data_type == 10:
1✔
56
                    # normalize the line ending before checking the length
57
                    if int(line[3:6]) != 188 or len(line.rstrip("\r\n")) != 188:
1✔
58
                        raise ParseError("Length should be 188")
1✔
59
                    transaction_id = line[6:12]
1✔
60
                    archival_reference = line[12:30].strip()
1✔
61
                    if len(archival_reference) < 18:
1✔
UNCOV
62
                        raise ParseError(
×
63
                            "Archival reference number invalid: " + archival_reference
64
                        )
65
                    transaction_date = datetime.datetime.strptime(line[30:36], "%y%m%d")
1✔
66
                    transaction_type = int(line[48])
1✔
67
                    if transaction_type < 1 or transaction_type > 4:
1✔
UNCOV
68
                        raise ParseError(
×
69
                            "Transaction type should be between 1 and 4, not "
70
                            + str(transaction_type)
71
                        )
72
                    code = line[49:52]
1✔
73
                    message = line[52:87].strip()
1✔
74
                    if message == "Viitemaksu":
1✔
75
                        message = None
1✔
76
                    amount = int(line[87:106]) / 100
1✔
77
                    peer = line[108:143]
1✔
78
                    peer = peer.replace("[", "Ä")
1✔
79
                    peer = peer.replace("]", "Å")
1✔
80
                    peer = peer.replace("\\", "Ö")
1✔
81
                    # tito format has leading zeroes in reference number, strip them
82
                    reference = line[159:179].strip().lstrip("0")
1✔
83

84
                    # Done parsing, add the transaction
85

86
                    try:
1✔
87
                        # Archival reference with date is unique
88
                        BankTransaction.objects.get(
1✔
89
                            archival_reference=archival_reference, date=transaction_date
90
                        )
91
                        exists = exists + 1
1✔
92
                    except BankTransaction.DoesNotExist:
1✔
93
                        transaction = BankTransaction.objects.create(
1✔
94
                            archival_reference=archival_reference,
95
                            date=transaction_date,
96
                            amount=amount,
97
                            reference_number=reference,
98
                            sender=peer,
99
                            transaction_id=transaction_id,
100
                            code=code,
101
                        )
102
                        BusinessLogic.new_transaction(transaction)
1✔
103
                        imported = imported + 1
1✔
104
            except ParseError as err:
1✔
105
                logger.error(f"Error parsing data: {err}")
1✔
106
                error = error + 1
1✔
107
                failedrows.append(line + " (" + str(err) + ")")
1✔
108

109
        results = {
1✔
110
            "imported": imported,
111
            "exists": exists,
112
            "error": error,
113
            "failedrows": failedrows,
114
        }
115
        logger.info(f"Data imported: {results} - now updating all users..")
1✔
116

117
        BusinessLogic.update_all_users()
1✔
118

119
        return results
1✔
120

121
    # Holvi TITO import. TITO spec here: ???
122
    # Note: this is the XLSX-based TITO.
123
    @staticmethod
1✔
124
    def import_holvi(f):
1✔
125
        holvi = HolviToolbox.parse_account_statement(f)
1✔
126
        imported = exists = error = 0
1✔
127
        failedrows = []
1✔
128
        for line in holvi:
1✔
129
            logger.debug(f"import_holvi - Processing line: {line}")
1✔
130
            try:
1✔
131
                if len(line) == 0:
1✔
UNCOV
132
                    raise ParseError("Empty line")
×
133
                archival_reference = line["Filing ID"].strip()
1✔
134
                if len(archival_reference) < 32:
1✔
UNCOV
135
                    raise ParseError(
×
136
                        "Archival reference number invalid: " + archival_reference
137
                    )
138
                transaction_date = line["Date_parsed"]
1✔
139
                message = line["Message"].strip()
1✔
140
                if message in ["Viitemaksu", "None"]:
1✔
141
                    message = None
1✔
142
                amount = Decimal(line["Amount"])
1✔
143
                peer = line["Counterparty"]
1✔
144
                # holvi reference has leading zeroes, clean them up here also
145
                reference = line["Reference"].strip().lstrip("0")
1✔
146

147
                # Done parsing, add the transaction
148

149
                try:
1✔
150
                    # Archival reference with date is unique
151
                    BankTransaction.objects.get(
1✔
152
                        archival_reference=archival_reference, date=transaction_date
153
                    )
154
                    exists = exists + 1
1✔
155
                except BankTransaction.DoesNotExist:
1✔
156
                    transaction = BankTransaction.objects.create(
1✔
157
                        archival_reference=archival_reference,
158
                        date=transaction_date,
159
                        amount=amount,
160
                        reference_number=reference,
161
                        sender=peer,
162
                    )
163
                    BusinessLogic.new_transaction(transaction)
1✔
164
                    imported = imported + 1
1✔
165
            except ParseError as err:
×
UNCOV
166
                logger.error(f"Error parsing data: {err}")
×
UNCOV
167
                error = error + 1
×
UNCOV
168
                failedrows.append(line + " (" + str(err) + ")")
×
169

170
        results = {
1✔
171
            "imported": imported,
172
            "exists": exists,
173
            "error": error,
174
            "failedrows": failedrows,
175
        }
176
        logger.info(f"Data imported: {results} - now updating all users..")
1✔
177

178
        BusinessLogic.update_all_users()
1✔
179

180
        return results
1✔
181

182
    @staticmethod
1✔
183
    def import_nordigen(data):
1✔
184
        """
185
        Importer for nordigen api response https://ob.nordigen.com/api/docs#/accounts/accounts_transactions_retrieve
186

187
        Takes the whole nordigen response but parses only the "booked" section from it (pending transactions do not count)
188
        """
189
        booked = data["transactions"]["booked"]
1✔
190
        imported = exists = error = 0
1✔
191

192
        failedrows = []
1✔
193
        for one in booked:
1✔
194
            logger.debug(f"import_nordigen - Processing entry: {one}")
1✔
195

196
            try:
1✔
197
                # map nordigen fields to our fields
198
                # NOTE: it seems that some banks give different fields
199
                # see: https://nordigen.com/en/docs/account-information/output/transactions/
200
                archival_reference = one["transactionId"]
1✔
201
                transaction_date = datetime.datetime.strptime(
1✔
202
                    one["bookingDate"], "%Y-%m-%d"
203
                ).date()
204
                if one["transactionAmount"]["currency"] != "EUR":
1✔
205
                    raise Exception("Cannot handle different currencies")
1✔
206
                amount = one["transactionAmount"]["amount"]
1✔
207
                reference = one.get("entryReference") or ""
1✔
208
                # clear leading zeroes from real reference
209
                reference = reference.lstrip("0")
1✔
210
                sender = one.get("debtorName", "")
1✔
211
                message = one.get("additionalInformation", "")
1✔
212

213
                # If reference is empty, try extracting the first number from the message
214
                if not reference:
1✔
215
                    match = re.search(r"\b0*(\d+)\b", message)
1✔
216
                    if match:
1✔
217
                        reference = match.group(1)
1✔
218
                        message = message + " (reference extracted from message)"
1✔
219

220
                try:
1✔
221
                    # Archival reference with date is unique
222
                    BankTransaction.objects.get(
1✔
223
                        archival_reference=archival_reference, date=transaction_date
224
                    )
225
                    exists = exists + 1
1✔
226
                except BankTransaction.DoesNotExist:
1✔
227
                    transaction = BankTransaction.objects.create(
1✔
228
                        archival_reference=archival_reference,
229
                        date=transaction_date,
230
                        transaction_id=archival_reference,
231
                        amount=amount,
232
                        reference_number=reference,
233
                        sender=sender,
234
                        message=message,
235
                    )
236
                    BusinessLogic.new_transaction(transaction)
1✔
237
                    imported = imported + 1
1✔
238

239
            except Exception as e:
1✔
240
                logger.error(f"Error: {e}")
1✔
241
                error = error + 1
1✔
242
                failedrows.append(str(one) + " (" + str(e) + ")")
1✔
243

244
        results = {
1✔
245
            "imported": imported,
246
            "exists": exists,
247
            "error": error,
248
            "failedrows": failedrows,
249
        }
250

251
        logger.info(f"Data imported: {results} - now updating all users..")
1✔
252
        BusinessLogic.update_all_users()
1✔
253

254
        return results
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc