Move test directory, add TxRequest
* ./tests is moved to ./trading/tests * Remove trading/tests.py * Add TxRequest model * Add tests for TxRequests Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
@@ -87,12 +87,26 @@ class User(PermissionsMixin, AbstractBaseUser):
|
||||
row["pk"]: row["in_circulation"]
|
||||
for row in self.commodity_set.values("pk", "in_circulation")
|
||||
}
|
||||
keys = set(ipos.keys()) | set(credits.keys()) | set(debits.keys())
|
||||
tx_requests = {
|
||||
row["source_sends"]: row["source_amount__sum"]
|
||||
for row in self.transaction_requests_sent.filter(
|
||||
status=TxRequest.OPEN
|
||||
).values("source_sends").annotate(Sum("source_amount"))
|
||||
}
|
||||
keys = (
|
||||
set(ipos.keys())
|
||||
| set(credits.keys())
|
||||
| set(debits.keys())
|
||||
| set(tx_requests.keys())
|
||||
)
|
||||
return frozendict(
|
||||
[
|
||||
(
|
||||
Commodity.objects.get(pk=pk),
|
||||
credits.get(pk, 0) + ipos.get(pk, 0) - debits.get(pk, 0),
|
||||
credits.get(pk, 0)
|
||||
+ ipos.get(pk, 0)
|
||||
- debits.get(pk, 0)
|
||||
- tx_requests.get(pk, 0),
|
||||
)
|
||||
for pk in keys
|
||||
]
|
||||
@@ -142,9 +156,7 @@ class Commodity(models.Model):
|
||||
)
|
||||
in_circulation = models.PositiveIntegerField()
|
||||
created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
|
||||
symbol = models.CharField(
|
||||
blank=True, unique=True, max_length=6, verbose_name=_("symbol")
|
||||
)
|
||||
symbol = models.CharField(blank=True, max_length=6, verbose_name=_("symbol"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("commodity")
|
||||
@@ -157,7 +169,7 @@ class Commodity(models.Model):
|
||||
if self.symbol:
|
||||
return f"<span>{self.symbol}</span>"
|
||||
else:
|
||||
return f"<span>units of {self.name}"
|
||||
return f"<span>units of {self.name}</span>"
|
||||
|
||||
|
||||
class MaxCommodityError(Exception):
|
||||
@@ -191,51 +203,51 @@ class Tx(models.Model):
|
||||
)
|
||||
amount = models.PositiveIntegerField(verbose_name=_("amount"), editable=False)
|
||||
commodity = models.ForeignKey(Commodity, on_delete=models.PROTECT, editable=False)
|
||||
request = models.ForeignKey(
|
||||
"TxRequest", null=True, default=None, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("transaction")
|
||||
verbose_name_plural = _("transactions")
|
||||
|
||||
|
||||
class TxRequests(models.Model):
|
||||
class TxRequest(models.Model):
|
||||
"""
|
||||
A transaction request between two users.
|
||||
"""
|
||||
|
||||
WAITING = "WAIT"
|
||||
OPEN = "OPEN"
|
||||
DECLINED = "DECL"
|
||||
REVISED = "REV"
|
||||
# REVISED = "REV"
|
||||
ACCEPTED = "ACC"
|
||||
|
||||
STATUS_CHOICES = [
|
||||
(WAITING, "Waiting"),
|
||||
(OPEN, "Open"),
|
||||
(DECLINED, "Declined"),
|
||||
(REVISED, "Revised"),
|
||||
# (REVISED, "Revised"),
|
||||
(ACCEPTED, "Accepted"),
|
||||
]
|
||||
|
||||
status = models.CharField(
|
||||
max_length=max([len(c) for c in STATUS_CHOICES]),
|
||||
choices=STATUS_CHOICES,
|
||||
default=WAITING,
|
||||
)
|
||||
previous_request = models.OneToOneField(
|
||||
"TxRequests",
|
||||
null=True,
|
||||
default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="revised_request",
|
||||
default=OPEN,
|
||||
)
|
||||
|
||||
# previous_request = models.OneToOneField(
|
||||
# "TxRequest",
|
||||
# null=True,
|
||||
# default=None,
|
||||
# on_delete=models.SET_NULL,
|
||||
# related_name="revised_request",
|
||||
# )
|
||||
|
||||
source = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
related_name="transaction_requests_sent",
|
||||
User, on_delete=models.CASCADE, related_name="transaction_requests_sent",
|
||||
)
|
||||
dest = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
related_name="transaction_requests_received",
|
||||
User, on_delete=models.CASCADE, related_name="transaction_requests_received",
|
||||
)
|
||||
|
||||
source_sends = models.ForeignKey(
|
||||
@@ -248,6 +260,81 @@ class TxRequests(models.Model):
|
||||
source_amount = models.PositiveIntegerField()
|
||||
dest_amount = models.PositiveIntegerField()
|
||||
|
||||
@staticmethod
|
||||
def open(
|
||||
source: User,
|
||||
dest: User,
|
||||
source_sends: Commodity,
|
||||
dest_sends: Commodity,
|
||||
source_amount: int,
|
||||
dest_amount: int,
|
||||
) -> "TxRequest":
|
||||
"""
|
||||
Opens a new transaction request.
|
||||
"""
|
||||
# Check balance
|
||||
source_balance = source.balance_of(source_sends)
|
||||
if source_balance < source_amount:
|
||||
raise BalanceError(source_balance, source, dest, source_amount, source_sends)
|
||||
req = TxRequest.objects.create(
|
||||
status=TxRequest.OPEN,
|
||||
source=source,
|
||||
dest=dest,
|
||||
source_sends=source_sends,
|
||||
dest_sends=dest_sends,
|
||||
source_amount=source_amount,
|
||||
dest_amount=dest_amount,
|
||||
)
|
||||
req.save()
|
||||
return req
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
Accepts an open transaction request.
|
||||
"""
|
||||
assert self.status == TxRequest.OPEN
|
||||
|
||||
# ensure destination balance before continuing
|
||||
dest_balance = self.dest.balance_of(self.dest_sends)
|
||||
if dest_balance < self.dest_amount:
|
||||
raise BalanceError(
|
||||
dest_balance, self.dest, self.source, self.dest_amount, self.dest_sends
|
||||
)
|
||||
|
||||
# update status
|
||||
self.status = TxRequest.ACCEPTED
|
||||
|
||||
# Create source transaction
|
||||
source_tx = Tx.objects.create(
|
||||
source=self.source,
|
||||
dest=self.dest,
|
||||
amount=self.source_amount,
|
||||
commodity=self.source_sends,
|
||||
request=self,
|
||||
)
|
||||
|
||||
# Create dest transaction
|
||||
dest_tx = Tx.objects.create(
|
||||
source=self.dest,
|
||||
dest=self.source,
|
||||
amount=self.dest_amount,
|
||||
commodity=self.dest_sends,
|
||||
request=self,
|
||||
)
|
||||
|
||||
# Save everything
|
||||
source_tx.save()
|
||||
dest_tx.save()
|
||||
self.save()
|
||||
|
||||
def decline(self):
|
||||
"""
|
||||
Declines an open transaction request.
|
||||
"""
|
||||
assert self.status == TxRequest.OPEN
|
||||
self.status = TxRequest.DECLINED
|
||||
self.save()
|
||||
|
||||
|
||||
class BalanceError(Exception):
|
||||
def __init__(
|
||||
|
||||
Reference in New Issue
Block a user