Skip to content
Snippets Groups Projects
Commit a264f1ff authored by inso's avatar inso
Browse files

Handle multi receivers transactions #581

parent e60b8332
No related branches found
No related tags found
No related merge requests found
import attr import attr
from duniterpy.documents import block_uid, endpoint from duniterpy.documents import block_uid, endpoint
from sakia.helpers import attrs_tuple_of_str
def _tuple_of_endpoints(value): def _tuple_of_endpoints(value):
...@@ -18,18 +19,6 @@ def _tuple_of_endpoints(value): ...@@ -18,18 +19,6 @@ def _tuple_of_endpoints(value):
raise TypeError("Can't convert {0} to list of endpoints".format(value)) raise TypeError("Can't convert {0} to list of endpoints".format(value))
def _tuple_of_hashes(ls):
if isinstance(ls, tuple):
return ls
elif isinstance(ls, list):
return tuple([str(a) for a in ls])
elif isinstance(ls, str):
if ls: # if string is not empty
return tuple([str(a) for a in ls.split('\n')])
else:
return tuple()
@attr.s() @attr.s()
class Node: class Node:
""" """
...@@ -73,7 +62,7 @@ class Node: ...@@ -73,7 +62,7 @@ class Node:
merkle_peers_root = attr.ib(convert=str, cmp=False, merkle_peers_root = attr.ib(convert=str, cmp=False,
default=MERKLE_EMPTY_ROOT) default=MERKLE_EMPTY_ROOT)
# Leaves of the merkle peers tree # Leaves of the merkle peers tree
merkle_peers_leaves = attr.ib(convert=_tuple_of_hashes, cmp=False, default=tuple()) merkle_peers_leaves = attr.ib(convert=attrs_tuple_of_str, cmp=False, default=tuple())
# Define if this node is a root node in Sakia # Define if this node is a root node in Sakia
root = attr.ib(convert=bool, cmp=False, default=False) root = attr.ib(convert=bool, cmp=False, default=False)
# If this node is a member or not # If this node is a member or not
......
import attr import attr
from duniterpy.documents import block_uid from duniterpy.documents import block_uid
from duniterpy.documents.transaction import reduce_base from duniterpy.documents.transaction import reduce_base
from sakia.helpers import attrs_tuple_of_str
import math import math
...@@ -57,7 +58,7 @@ def parse_transaction_doc(tx_doc, pubkey, block_number, mediantime, txid): ...@@ -57,7 +58,7 @@ def parse_transaction_doc(tx_doc, pubkey, block_number, mediantime, txid):
timestamp=mediantime, timestamp=mediantime,
signature=tx_doc.signatures[0], signature=tx_doc.signatures[0],
issuer=tx_doc.issuers[0], issuer=tx_doc.issuers[0],
receiver=receivers[0], receivers=receivers,
amount=amount, amount=amount,
amount_base=amount_base, amount_base=amount_base,
comment=tx_doc.comment, comment=tx_doc.comment,
...@@ -99,7 +100,7 @@ class Transaction: ...@@ -99,7 +100,7 @@ class Transaction:
timestamp = attr.ib(convert=int, cmp=False) timestamp = attr.ib(convert=int, cmp=False)
signature = attr.ib(convert=str, cmp=False) signature = attr.ib(convert=str, cmp=False)
issuer = attr.ib(convert=str, cmp=False) issuer = attr.ib(convert=str, cmp=False)
receiver = attr.ib(convert=str, cmp=False) receivers = attr.ib(convert=attrs_tuple_of_str, cmp=False)
amount = attr.ib(convert=int, cmp=False) amount = attr.ib(convert=int, cmp=False)
amount_base = attr.ib(convert=int, cmp=False) amount_base = attr.ib(convert=int, cmp=False)
comment = attr.ib(convert=str, cmp=False) comment = attr.ib(convert=str, cmp=False)
......
...@@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS transactions( ...@@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS transactions(
ts INT, ts INT,
signature VARCHAR(100), signature VARCHAR(100),
issuer VARCHAR(50), issuer VARCHAR(50),
receiver VARCHAR(50), receiver TEXT,
amount INT, amount INT,
amountbase INT, amountbase INT,
comment VARCHAR(255), comment VARCHAR(255),
......
...@@ -15,7 +15,10 @@ class TransactionsRepo: ...@@ -15,7 +15,10 @@ class TransactionsRepo:
Commit a transaction to the database Commit a transaction to the database
:param sakia.data.entities.Transaction transaction: the transaction to commit :param sakia.data.entities.Transaction transaction: the transaction to commit
""" """
transaction_tuple = attr.astuple(transaction) transaction_tuple = attr.astuple(transaction, tuple_factory=list)
transaction_tuple[7] = "\n".join([str(n) for n in transaction_tuple[7]])
values = ",".join(['?'] * len(transaction_tuple)) values = ",".join(['?'] * len(transaction_tuple))
self._conn.execute("INSERT INTO transactions VALUES ({0})".format(values), transaction_tuple) self._conn.execute("INSERT INTO transactions VALUES ({0})".format(values), transaction_tuple)
...@@ -93,7 +96,7 @@ class TransactionsRepo: ...@@ -93,7 +96,7 @@ class TransactionsRepo:
:rtype: List[sakia.data.entities.Transaction] :rtype: List[sakia.data.entities.Transaction]
""" """
request = """SELECT * FROM transactions request = """SELECT * FROM transactions
WHERE currency=? AND (issuer=? or receiver=?) WHERE currency=? AND (issuer=? or receiver LIKE ?)
ORDER BY {sort_by} {sort_order} ORDER BY {sort_by} {sort_order}
LIMIT {limit} OFFSET {offset}""" \ LIMIT {limit} OFFSET {offset}""" \
.format(offset=offset, .format(offset=offset,
...@@ -101,7 +104,7 @@ class TransactionsRepo: ...@@ -101,7 +104,7 @@ class TransactionsRepo:
sort_by=sort_by, sort_by=sort_by,
sort_order=sort_order sort_order=sort_order
) )
c = self._conn.execute(request, (currency, pubkey, pubkey)) c = self._conn.execute(request, (currency, pubkey, "%" + pubkey + "%"))
datas = c.fetchall() datas = c.fetchall()
if datas: if datas:
return [Transaction(*data) for data in datas] return [Transaction(*data) for data in datas]
......
...@@ -89,7 +89,7 @@ class TransferController(QObject): ...@@ -89,7 +89,7 @@ class TransferController(QObject):
def send_transfer_again(cls, parent, app, connection, resent_transfer): def send_transfer_again(cls, parent, app, connection, resent_transfer):
dialog = cls.create(parent, app) dialog = cls.create(parent, app)
dialog.view.combo_connections.setCurrentText(connection.title()) dialog.view.combo_connections.setCurrentText(connection.title())
dialog.view.edit_pubkey.setText(resent_transfer.receiver) dialog.view.edit_pubkey.setText(resent_transfer.receivers[0])
dialog.view.radio_pubkey.setChecked(True) dialog.view.radio_pubkey.setChecked(True)
dialog.refresh() dialog.refresh()
...@@ -105,7 +105,7 @@ class TransferController(QObject): ...@@ -105,7 +105,7 @@ class TransferController(QObject):
connections_processor = ConnectionsProcessor.instanciate(app) connections_processor = ConnectionsProcessor.instanciate(app)
wallet_index = connections_processor.connections().index(connection) wallet_index = connections_processor.connections().index(connection)
dialog.view.combo_connections.setCurrentIndex(wallet_index) dialog.view.combo_connections.setCurrentIndex(wallet_index)
dialog.view.edit_pubkey.setText(resent_transfer.receiver) dialog.view.edit_pubkey.setText(resent_transfer.receivers[0])
dialog.view.radio_pubkey.setChecked(True) dialog.view.radio_pubkey.setChecked(True)
dialog.view.edit_message.setText(resent_transfer.comment) dialog.view.edit_message.setText(resent_transfer.comment)
......
...@@ -78,7 +78,7 @@ class TxHistoryController(QObject): ...@@ -78,7 +78,7 @@ class TxHistoryController(QObject):
if transfer.issuer != self.model.connection.pubkey: if transfer.issuer != self.model.connection.pubkey:
pubkey = transfer.issuer pubkey = transfer.issuer
else: else:
pubkey = transfer.receiver pubkey = transfer.receivers[0]
identity = Identity(currency=transfer.currency, pubkey=pubkey) identity = Identity(currency=transfer.currency, pubkey=pubkey)
menu = ContextMenu.from_data(self.view, self.model.app, self.model.connection, (identity, transfer)) menu = ContextMenu.from_data(self.view, self.model.app, self.model.connection, (identity, transfer))
menu.view_identity_in_wot.connect(self.view_in_wot) menu.view_identity_in_wot.connect(self.view_in_wot)
......
from PyQt5.QtGui import QTextDocument, QAbstractTextDocumentLayout
from PyQt5.QtWidgets import QStyledItemDelegate, QApplication, QStyle
from PyQt5.QtCore import QSize
from .table_model import HistoryTableModel
class TxHistoryDelegate(QStyledItemDelegate):
def paint(self, painter, option, index):
self.initStyleOption(option, index)
style = QApplication.style()
doc = QTextDocument()
if index.column() == HistoryTableModel.columns_types.index('uid'):
doc.setHtml(option.text)
else:
doc.setPlainText(option.text)
option.text = ""
style.drawControl(QStyle.CE_ItemViewItem, option, painter)
ctx = QAbstractTextDocumentLayout.PaintContext()
text_rect = style.subElementRect(QStyle.SE_ItemViewItemText, option)
painter.save()
painter.translate(text_rect.topLeft())
painter.setClipRect(text_rect.translated(-text_rect.topLeft()))
doc.documentLayout().draw(painter, ctx)
painter.restore()
def sizeHint(self, option, index):
self.initStyleOption(option, index)
doc = QTextDocument()
if index.column() == HistoryTableModel.columns_types.index('uid'):
doc.setHtml(option.text)
else:
doc.setPlainText("")
doc.setTextWidth(option.rect.width())
return QSize(doc.idealWidth(), doc.size().height())
...@@ -2,7 +2,7 @@ import datetime ...@@ -2,7 +2,7 @@ import datetime
import logging import logging
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \ from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \
QDateTime, QLocale, QModelIndex QDateTime, QLocale, QModelIndex, QT_TRANSLATE_NOOP
from PyQt5.QtGui import QFont, QColor from PyQt5.QtGui import QFont, QColor
from sakia.data.entities import Transaction from sakia.data.entities import Transaction
from sakia.constants import MAX_CONFIRMATIONS from sakia.constants import MAX_CONFIRMATIONS
...@@ -93,7 +93,7 @@ class TxFilterProxyModel(QSortFilterProxyModel): ...@@ -93,7 +93,7 @@ class TxFilterProxyModel(QSortFilterProxyModel):
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
if source_index.column() == model.columns_types.index('uid'): if source_index.column() == model.columns_types.index('uid'):
return source_data return "<p>" + source_data.replace('\n', "<br>") + "</p>"
if source_index.column() == model.columns_types.index('date'): if source_index.column() == model.columns_types.index('date'):
return QLocale.toString( return QLocale.toString(
QLocale(), QLocale(),
...@@ -160,6 +160,26 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -160,6 +160,26 @@ class HistoryTableModel(QAbstractTableModel):
DIVIDEND = 32 DIVIDEND = 32
columns_types = (
'date',
'uid',
'amount',
'comment',
'state',
'txid',
'pubkey',
'block_number',
'txhash',
'raw_data'
)
columns_headers = (
QT_TRANSLATE_NOOP("HistoryTableModel", 'Date'),
QT_TRANSLATE_NOOP("HistoryTableModel", 'UID/Public key'),
QT_TRANSLATE_NOOP("HistoryTableModel", 'Amount'),
QT_TRANSLATE_NOOP("HistoryTableModel", 'Comment')
)
def __init__(self, parent, app, connection, identities_service, transactions_service): def __init__(self, parent, app, connection, identities_service, transactions_service):
""" """
History of all transactions History of all transactions
...@@ -177,26 +197,6 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -177,26 +197,6 @@ class HistoryTableModel(QAbstractTableModel):
self.transactions_service = transactions_service self.transactions_service = transactions_service
self.transfers_data = [] self.transfers_data = []
self.columns_types = (
'date',
'uid',
'amount',
'comment',
'state',
'txid',
'pubkey',
'block_number',
'txhash',
'raw_data'
)
self.column_headers = (
lambda: self.tr('Date'),
lambda: self.tr('UID/Public key'),
lambda: self.tr('Amount'),
lambda: self.tr('Comment')
)
def transfers(self): def transfers(self):
""" """
Transfer Transfer
...@@ -212,11 +212,11 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -212,11 +212,11 @@ class HistoryTableModel(QAbstractTableModel):
return self.transactions_service.dividends(self.connection.pubkey) return self.transactions_service.dividends(self.connection.pubkey)
def add_transfer(self, transfer): def add_transfer(self, transfer):
if self.connection.pubkey in (transfer.issuer, transfer.receiver): if self.connection.pubkey in (transfer.issuer, *transfer.receivers):
self.beginInsertRows(QModelIndex(), len(self.transfers_data), len(self.transfers_data)) self.beginInsertRows(QModelIndex(), len(self.transfers_data), len(self.transfers_data))
if transfer.issuer == self.connection.pubkey: if transfer.issuer == self.connection.pubkey:
self.transfers_data.append(self.data_sent(transfer)) self.transfers_data.append(self.data_sent(transfer))
if transfer.receiver == self.connection.pubkey: if self.connection.pubkey in transfer.receivers:
self.transfers_data.append(self.data_received(transfer)) self.transfers_data.append(self.data_received(transfer))
self.endInsertRows() self.endInsertRows()
...@@ -228,7 +228,7 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -228,7 +228,7 @@ class HistoryTableModel(QAbstractTableModel):
def change_transfer(self, transfer): def change_transfer(self, transfer):
for i, data in enumerate(self.transfers_data): for i, data in enumerate(self.transfers_data):
if data[self.columns_types.index('txhash')] == transfer.sha_hash: if data[HistoryTableModel.columns_types.index('txhash')] == transfer.sha_hash:
if transfer.state == Transaction.DROPPED: if transfer.state == Transaction.DROPPED:
self.beginRemoveRows(QModelIndex(), i, i) self.beginRemoveRows(QModelIndex(), i, i)
self.transfers_data.pop(i) self.transfers_data.pop(i)
...@@ -236,10 +236,10 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -236,10 +236,10 @@ class HistoryTableModel(QAbstractTableModel):
else: else:
if transfer.issuer == self.connection.pubkey: if transfer.issuer == self.connection.pubkey:
self.transfers_data[i] = self.data_sent(transfer) self.transfers_data[i] = self.data_sent(transfer)
self.dataChanged.emit(self.index(i, 0), self.index(i, len(self.columns_types))) self.dataChanged.emit(self.index(i, 0), self.index(i, len(HistoryTableModel.columns_types)))
if transfer.receiver == self.connection.pubkey: if self.connection.pubkey in transfer.receivers:
self.transfers_data[i] = self.data_received(transfer) self.transfers_data[i] = self.data_received(transfer)
self.dataChanged.emit(self.index(i, 0), self.index(i, len(self.columns_types))) self.dataChanged.emit(self.index(i, 0), self.index(i, len(HistoryTableModel.columns_types)))
return return
def data_received(self, transfer): def data_received(self, transfer):
...@@ -274,16 +274,18 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -274,16 +274,18 @@ class HistoryTableModel(QAbstractTableModel):
block_number = transfer.written_block block_number = transfer.written_block
amount = transfer.amount * 10**transfer.amount_base * -1 amount = transfer.amount * 10**transfer.amount_base * -1
identity = self.identities_service.get_identity(transfer.receiver) receivers = []
for receiver in transfer.receivers:
identity = self.identities_service.get_identity(receiver)
if identity: if identity:
receiver = identity.uid receivers.append(identity.uid)
else: else:
receiver = transfer.receiver receivers.append(receiver)
date_ts = transfer.timestamp date_ts = transfer.timestamp
txid = transfer.txid txid = transfer.txid
return (date_ts, receiver, amount, transfer.comment, transfer.state, txid, return (date_ts, receivers, amount, transfer.comment, transfer.state, txid,
transfer.receiver, block_number, transfer.sha_hash, transfer) "\n".join(transfer.receivers), block_number, transfer.sha_hash, transfer)
def data_dividend(self, dividend): def data_dividend(self, dividend):
""" """
...@@ -312,7 +314,7 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -312,7 +314,7 @@ class HistoryTableModel(QAbstractTableModel):
if transfer.state != Transaction.DROPPED: if transfer.state != Transaction.DROPPED:
if transfer.issuer == self.connection.pubkey: if transfer.issuer == self.connection.pubkey:
self.transfers_data.append(self.data_sent(transfer)) self.transfers_data.append(self.data_sent(transfer))
if transfer.receiver == self.connection.pubkey: if self.connection.pubkey in transfer.receivers:
self.transfers_data.append(self.data_received(transfer)) self.transfers_data.append(self.data_received(transfer))
dividends = self.dividends() dividends = self.dividends()
for dividend in dividends: for dividend in dividends:
...@@ -323,18 +325,18 @@ class HistoryTableModel(QAbstractTableModel): ...@@ -323,18 +325,18 @@ class HistoryTableModel(QAbstractTableModel):
return len(self.transfers_data) return len(self.transfers_data)
def columnCount(self, parent): def columnCount(self, parent):
return len(self.columns_types) return len(HistoryTableModel.columns_types)
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if role == Qt.DisplayRole: if role == Qt.DisplayRole:
if self.columns_types[section] == 'amount': if HistoryTableModel.columns_types[section] == 'amount':
dividend, base = self.blockchain_processor.last_ud(self.transactions_service.currency) dividend, base = self.blockchain_processor.last_ud(self.transactions_service.currency)
header = '{:}'.format(self.column_headers[section]()) header = '{:}'.format(HistoryTableModel.columns_headers[section])
if self.app.current_ref.base_str(base): if self.app.current_ref.base_str(base):
header += " ({:})".format(self.app.current_ref.base_str(base)) header += " ({:})".format(self.app.current_ref.base_str(base))
return header return header
return self.column_headers[section]() return HistoryTableModel.columns_headers[section]
def data(self, index, role): def data(self, index, role):
row = index.row() row = index.row()
......
from PyQt5.QtCore import QDateTime, QEvent from PyQt5.QtCore import QDateTime, QEvent
from PyQt5.QtWidgets import QWidget, QAbstractItemView, QHeaderView from PyQt5.QtWidgets import QWidget, QAbstractItemView, QHeaderView
from .delegate import TxHistoryDelegate
from .txhistory_uic import Ui_TxHistoryWidget from .txhistory_uic import Ui_TxHistoryWidget
...@@ -30,6 +31,7 @@ class TxHistoryView(QWidget, Ui_TxHistoryWidget): ...@@ -30,6 +31,7 @@ class TxHistoryView(QWidget, Ui_TxHistoryWidget):
self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_history.setSortingEnabled(True) self.table_history.setSortingEnabled(True)
self.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive) self.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
self.table_history.setItemDelegate(TxHistoryDelegate())
model.modelReset.connect(self.table_history.resizeColumnsToContents) model.modelReset.connect(self.table_history.resizeColumnsToContents)
def set_minimum_maximum_datetime(self, minimum, maximum): def set_minimum_maximum_datetime(self, minimum, maximum):
......
...@@ -31,3 +31,15 @@ def single_instance_lock(): ...@@ -31,3 +31,15 @@ def single_instance_lock():
def cleanup_lock(lock): def cleanup_lock(lock):
if lock.isAttached(): if lock.isAttached():
lock.detach() lock.detach()
def attrs_tuple_of_str(ls):
if isinstance(ls, tuple):
return ls
elif isinstance(ls, list):
return tuple([str(a) for a in ls])
elif isinstance(ls, str):
if ls: # if string is not empty
return tuple([str(a) for a in ls.split('\n')])
else:
return tuple()
\ No newline at end of file
...@@ -28,7 +28,7 @@ def test_add_get_drop_transaction(meta_repo): ...@@ -28,7 +28,7 @@ def test_add_get_drop_transaction(meta_repo):
assert transaction.amount == 1565 assert transaction.amount == 1565
assert transaction.amount_base == 1 assert transaction.amount_base == 1
assert transaction.issuer == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" assert transaction.issuer == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
assert transaction.receiver == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" assert transaction.receivers[0] == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
assert transaction.comment == "" assert transaction.comment == ""
assert transaction.txid == 0 assert transaction.txid == 0
transactions_repo.drop(transaction) transactions_repo.drop(transaction)
...@@ -66,7 +66,7 @@ def test_add_get_multiple_transaction(meta_repo): ...@@ -66,7 +66,7 @@ def test_add_get_multiple_transaction(meta_repo):
Transaction.TO_SEND)) Transaction.TO_SEND))
transactions = transactions_repo.get_all(currency="testcurrency") transactions = transactions_repo.get_all(currency="testcurrency")
assert "testcurrency" in [t.currency for t in transactions] assert "testcurrency" in [t.currency for t in transactions]
assert "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" in [t.receiver for t in transactions] assert "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ" in [t.receivers[0] for t in transactions]
assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in [t.issuer for t in transactions] assert "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn" in [t.issuer for t in transactions]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment