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

Handle multi receivers transactions #581

parent e60b8332
Branches
Tags
No related merge requests found
import attr
from duniterpy.documents import block_uid, endpoint
from sakia.helpers import attrs_tuple_of_str
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))
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()
class Node:
"""
......@@ -73,7 +62,7 @@ class Node:
merkle_peers_root = attr.ib(convert=str, cmp=False,
default=MERKLE_EMPTY_ROOT)
# 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
root = attr.ib(convert=bool, cmp=False, default=False)
# If this node is a member or not
......
import attr
from duniterpy.documents import block_uid
from duniterpy.documents.transaction import reduce_base
from sakia.helpers import attrs_tuple_of_str
import math
......@@ -57,7 +58,7 @@ def parse_transaction_doc(tx_doc, pubkey, block_number, mediantime, txid):
timestamp=mediantime,
signature=tx_doc.signatures[0],
issuer=tx_doc.issuers[0],
receiver=receivers[0],
receivers=receivers,
amount=amount,
amount_base=amount_base,
comment=tx_doc.comment,
......@@ -99,7 +100,7 @@ class Transaction:
timestamp = attr.ib(convert=int, cmp=False)
signature = 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_base = attr.ib(convert=int, cmp=False)
comment = attr.ib(convert=str, cmp=False)
......
......@@ -75,7 +75,7 @@ CREATE TABLE IF NOT EXISTS transactions(
ts INT,
signature VARCHAR(100),
issuer VARCHAR(50),
receiver VARCHAR(50),
receiver TEXT,
amount INT,
amountbase INT,
comment VARCHAR(255),
......
......@@ -15,7 +15,10 @@ class TransactionsRepo:
Commit a transaction to the database
: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))
self._conn.execute("INSERT INTO transactions VALUES ({0})".format(values), transaction_tuple)
......@@ -93,7 +96,7 @@ class TransactionsRepo:
:rtype: List[sakia.data.entities.Transaction]
"""
request = """SELECT * FROM transactions
WHERE currency=? AND (issuer=? or receiver=?)
WHERE currency=? AND (issuer=? or receiver LIKE ?)
ORDER BY {sort_by} {sort_order}
LIMIT {limit} OFFSET {offset}""" \
.format(offset=offset,
......@@ -101,7 +104,7 @@ class TransactionsRepo:
sort_by=sort_by,
sort_order=sort_order
)
c = self._conn.execute(request, (currency, pubkey, pubkey))
c = self._conn.execute(request, (currency, pubkey, "%" + pubkey + "%"))
datas = c.fetchall()
if datas:
return [Transaction(*data) for data in datas]
......
......@@ -89,7 +89,7 @@ class TransferController(QObject):
def send_transfer_again(cls, parent, app, connection, resent_transfer):
dialog = cls.create(parent, app)
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.refresh()
......@@ -105,7 +105,7 @@ class TransferController(QObject):
connections_processor = ConnectionsProcessor.instanciate(app)
wallet_index = connections_processor.connections().index(connection)
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.edit_message.setText(resent_transfer.comment)
......
......@@ -78,7 +78,7 @@ class TxHistoryController(QObject):
if transfer.issuer != self.model.connection.pubkey:
pubkey = transfer.issuer
else:
pubkey = transfer.receiver
pubkey = transfer.receivers[0]
identity = Identity(currency=transfer.currency, pubkey=pubkey)
menu = ContextMenu.from_data(self.view, self.model.app, self.model.connection, (identity, transfer))
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
import logging
from PyQt5.QtCore import QAbstractTableModel, Qt, QVariant, QSortFilterProxyModel, \
QDateTime, QLocale, QModelIndex
QDateTime, QLocale, QModelIndex, QT_TRANSLATE_NOOP
from PyQt5.QtGui import QFont, QColor
from sakia.data.entities import Transaction
from sakia.constants import MAX_CONFIRMATIONS
......@@ -93,7 +93,7 @@ class TxFilterProxyModel(QSortFilterProxyModel):
if role == Qt.DisplayRole:
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'):
return QLocale.toString(
QLocale(),
......@@ -160,6 +160,26 @@ class HistoryTableModel(QAbstractTableModel):
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):
"""
History of all transactions
......@@ -177,26 +197,6 @@ class HistoryTableModel(QAbstractTableModel):
self.transactions_service = transactions_service
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):
"""
Transfer
......@@ -212,11 +212,11 @@ class HistoryTableModel(QAbstractTableModel):
return self.transactions_service.dividends(self.connection.pubkey)
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))
if transfer.issuer == self.connection.pubkey:
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.endInsertRows()
......@@ -228,7 +228,7 @@ class HistoryTableModel(QAbstractTableModel):
def change_transfer(self, transfer):
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:
self.beginRemoveRows(QModelIndex(), i, i)
self.transfers_data.pop(i)
......@@ -236,10 +236,10 @@ class HistoryTableModel(QAbstractTableModel):
else:
if transfer.issuer == self.connection.pubkey:
self.transfers_data[i] = self.data_sent(transfer)
self.dataChanged.emit(self.index(i, 0), self.index(i, len(self.columns_types)))
if transfer.receiver == self.connection.pubkey:
self.dataChanged.emit(self.index(i, 0), self.index(i, len(HistoryTableModel.columns_types)))
if self.connection.pubkey in transfer.receivers:
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
def data_received(self, transfer):
......@@ -274,16 +274,18 @@ class HistoryTableModel(QAbstractTableModel):
block_number = transfer.written_block
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:
receiver = identity.uid
receivers.append(identity.uid)
else:
receiver = transfer.receiver
receivers.append(receiver)
date_ts = transfer.timestamp
txid = transfer.txid
return (date_ts, receiver, amount, transfer.comment, transfer.state, txid,
transfer.receiver, block_number, transfer.sha_hash, transfer)
return (date_ts, receivers, amount, transfer.comment, transfer.state, txid,
"\n".join(transfer.receivers), block_number, transfer.sha_hash, transfer)
def data_dividend(self, dividend):
"""
......@@ -312,7 +314,7 @@ class HistoryTableModel(QAbstractTableModel):
if transfer.state != Transaction.DROPPED:
if transfer.issuer == self.connection.pubkey:
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))
dividends = self.dividends()
for dividend in dividends:
......@@ -323,18 +325,18 @@ class HistoryTableModel(QAbstractTableModel):
return len(self.transfers_data)
def columnCount(self, parent):
return len(self.columns_types)
return len(HistoryTableModel.columns_types)
def headerData(self, section, orientation, role):
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)
header = '{:}'.format(self.column_headers[section]())
header = '{:}'.format(HistoryTableModel.columns_headers[section])
if self.app.current_ref.base_str(base):
header += " ({:})".format(self.app.current_ref.base_str(base))
return header
return self.column_headers[section]()
return HistoryTableModel.columns_headers[section]
def data(self, index, role):
row = index.row()
......
from PyQt5.QtCore import QDateTime, QEvent
from PyQt5.QtWidgets import QWidget, QAbstractItemView, QHeaderView
from .delegate import TxHistoryDelegate
from .txhistory_uic import Ui_TxHistoryWidget
......@@ -30,6 +31,7 @@ class TxHistoryView(QWidget, Ui_TxHistoryWidget):
self.table_history.setSelectionBehavior(QAbstractItemView.SelectRows)
self.table_history.setSortingEnabled(True)
self.table_history.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
self.table_history.setItemDelegate(TxHistoryDelegate())
model.modelReset.connect(self.table_history.resizeColumnsToContents)
def set_minimum_maximum_datetime(self, minimum, maximum):
......
......@@ -31,3 +31,15 @@ def single_instance_lock():
def cleanup_lock(lock):
if lock.isAttached():
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):
assert transaction.amount == 1565
assert transaction.amount_base == 1
assert transaction.issuer == "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ"
assert transaction.receiver == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
assert transaction.receivers[0] == "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn"
assert transaction.comment == ""
assert transaction.txid == 0
transactions_repo.drop(transaction)
......@@ -66,7 +66,7 @@ def test_add_get_multiple_transaction(meta_repo):
Transaction.TO_SEND))
transactions = transactions_repo.get_all(currency="testcurrency")
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]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment