Doing a sort of catch-up on previous work after returning 2 months later

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-03-21 14:57:14 -04:00
parent 7576242a71
commit 80ad39eb6f
12 changed files with 291 additions and 82 deletions

View File

@@ -12,6 +12,7 @@ django-bootstrap4 = "*"
django-invitations = "*" django-invitations = "*"
django-hashid-field = "*" django-hashid-field = "*"
frozendict = "*" frozendict = "*"
django-guardian = "*"
[requires] [requires]
python_version = "3.8" python_version = "3.8"

127
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "36bd6ea9711c247710904103a81d2a2abae1000e988ebee11d5eaa22697d5cdf" "sha256": "c64de64f000ab3904d359eac1b8e630940fa6d7baa0447215243b565ae03c82d"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -40,20 +40,28 @@
"index": "pypi", "index": "pypi",
"version": "==1.1.1" "version": "==1.1.1"
}, },
"django-hashid-field": { "django-guardian": {
"hashes": [ "hashes": [
"sha256:328e83f13ab0eedd4ed8a384bde8b7a4ce18ee8ee1e1d149247ba7611fc7addb", "sha256:8cacf49ebcc1e545f0a8997971eec0fe109f5ed31fc2a569a7bf5615453696e2",
"sha256:cff0805a4c4243d1c30d180e70efbe08395bddce4177d4c968c1f9bf0542a5a5" "sha256:ac81e88372fdf1795d84ba065550e739b42e9c6d07cdf201cf5bbf9efa7f396c"
], ],
"index": "pypi", "index": "pypi",
"version": "==3.0.0" "version": "==2.2.0"
},
"django-hashid-field": {
"hashes": [
"sha256:98096dc25ee558cd26628b33149da2d74722c925aa484c12347d75e6df2a1d19",
"sha256:a28b99fc779c62cbd42e910809db063841f055db37424b644127134d17631339"
],
"index": "pypi",
"version": "==3.1.1"
}, },
"django-invitations": { "django-invitations": {
"hashes": [ "hashes": [
"sha256:fe0a4c822bf695f7dac9f828c95f1dcee42dc590ed2943bd4d6ec8a1e03ab08e" "sha256:6077807aa641c7abae9ca418e757c8e3940fb0ce60499dba24c100ad57c61442"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.9.2" "version": "==1.9.3"
}, },
"frozendict": { "frozendict": {
"hashes": [ "hashes": [
@@ -77,17 +85,17 @@
}, },
"soupsieve": { "soupsieve": {
"hashes": [ "hashes": [
"sha256:bdb0d917b03a1369ce964056fc195cfdff8819c40de04695a80bc813c3cfa1f5", "sha256:e914534802d7ffd233242b785229d5ba0766a7f487385e3f714446a07bf540ae",
"sha256:e2c1c5dee4a1c36bcb790e0fabd5492d874b8ebd4617622c4f6a731701060dda" "sha256:fcd71e08c0aee99aca1b73f45478549ee7e7fc006d51b37bec9e9def7dc22b69"
], ],
"version": "==1.9.5" "version": "==2.0"
}, },
"sqlparse": { "sqlparse": {
"hashes": [ "hashes": [
"sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177", "sha256:022fb9c87b524d1f7862b3037e541f68597a730a8843245c349fc93e1643dc4e",
"sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873" "sha256:e162203737712307dfe78860cc56c8da8a852ab2ee33750e33aeadf38d12c548"
], ],
"version": "==0.3.0" "version": "==0.3.1"
} }
}, },
"develop": { "develop": {
@@ -115,10 +123,10 @@
}, },
"click": { "click": {
"hashes": [ "hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"
], ],
"version": "==7.0" "version": "==7.1.1"
}, },
"pathspec": { "pathspec": {
"hashes": [ "hashes": [
@@ -129,29 +137,29 @@
}, },
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525", "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431",
"sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b", "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242",
"sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576", "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1",
"sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5", "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d",
"sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0", "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045",
"sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35", "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b",
"sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003", "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400",
"sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d", "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa",
"sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161", "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0",
"sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26", "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69",
"sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9", "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74",
"sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1", "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb",
"sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146", "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26",
"sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f", "sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5",
"sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149", "sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2",
"sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351", "sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce",
"sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461", "sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab",
"sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b", "sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e",
"sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242", "sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70",
"sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c", "sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc",
"sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77" "sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"
], ],
"version": "==2020.1.8" "version": "==2020.2.20"
}, },
"toml": { "toml": {
"hashes": [ "hashes": [
@@ -162,28 +170,29 @@
}, },
"typed-ast": { "typed-ast": {
"hashes": [ "hashes": [
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
"sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
"sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
"sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
"sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
"sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
"sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
"sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
"sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
"sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
"sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
"sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
"sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
"sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
"sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
"sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
"sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
"sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
], ],
"version": "==1.4.0" "version": "==1.4.1"
} }
} }
} }

View File

@@ -51,10 +51,12 @@ INSTALLED_APPS = [
# Third party apps # Third party apps
"bootstrap4", "bootstrap4",
"guardian",
] ]
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend", "django.contrib.auth.backends.ModelBackend",
"guardian.backends.ObjectPermissionBackend",
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@@ -0,0 +1,17 @@
.debit {
color: red;
/*font-weight: bold;*/
}
.debit:before {
content: "(";
}
.debit:after {
content: ")";
}
.credit {
color: black;
/*font-weight: bold;*/
}

View File

@@ -1,4 +1,4 @@
# Generated by Django 2.2 on 2020-01-17 01:24 # Generated by Django 2.2 on 2020-03-21 18:57
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@@ -58,12 +58,12 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('OPEN', 'Open'), ('DECL', 'Declined'), ('ACC', 'Accepted')], default='OPEN', max_length=2)), ('status', models.CharField(choices=[('OPEN', 'Open'), ('DECL', 'Declined'), ('ACC', 'Accepted')], default='OPEN', max_length=2)),
('source_amount', models.PositiveIntegerField()), ('source_amount', models.PositiveIntegerField(editable=False)),
('dest_amount', models.PositiveIntegerField()), ('dest_amount', models.PositiveIntegerField(editable=False)),
('dest', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transaction_requests_received', to=settings.AUTH_USER_MODEL)), ('dest', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='transaction_requests_received', to=settings.AUTH_USER_MODEL)),
('dest_sends', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='trading.Commodity')), ('dest_sends', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='trading.Commodity')),
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transaction_requests_sent', to=settings.AUTH_USER_MODEL)), ('source', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='transaction_requests_sent', to=settings.AUTH_USER_MODEL)),
('source_sends', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='trading.Commodity')), ('source_sends', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='trading.Commodity')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(

View File

@@ -7,6 +7,7 @@ from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from frozendict import frozendict from frozendict import frozendict
from guardian.shortcuts import assign_perm
from hashid_field import HashidAutoField from hashid_field import HashidAutoField
from trading.managers import UserManager from trading.managers import UserManager
@@ -89,9 +90,9 @@ class User(PermissionsMixin, AbstractBaseUser):
} }
tx_requests = { tx_requests = {
row["source_sends"]: row["source_amount__sum"] row["source_sends"]: row["source_amount__sum"]
for row in self.transaction_requests_sent.filter( for row in self.transaction_requests_sent.filter(status=TxRequest.OPEN)
status=TxRequest.OPEN .values("source_sends")
).values("source_sends").annotate(Sum("source_amount")) .annotate(Sum("source_amount"))
} }
keys = ( keys = (
set(ipos.keys()) set(ipos.keys())
@@ -244,21 +245,27 @@ class TxRequest(models.Model):
# ) # )
source = models.ForeignKey( source = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="transaction_requests_sent", User,
on_delete=models.CASCADE,
related_name="transaction_requests_sent",
editable=False,
) )
dest = models.ForeignKey( dest = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="transaction_requests_received", User,
on_delete=models.CASCADE,
related_name="transaction_requests_received",
editable=False,
) )
source_sends = models.ForeignKey( source_sends = models.ForeignKey(
Commodity, on_delete=models.CASCADE, related_name="+", null=True, Commodity, on_delete=models.CASCADE, related_name="+", null=True, editable=False,
) )
dest_sends = models.ForeignKey( dest_sends = models.ForeignKey(
Commodity, on_delete=models.CASCADE, related_name="+", null=True, Commodity, on_delete=models.CASCADE, related_name="+", null=True, editable=False,
) )
source_amount = models.PositiveIntegerField() source_amount = models.PositiveIntegerField(editable=False)
dest_amount = models.PositiveIntegerField() dest_amount = models.PositiveIntegerField(editable=False)
@staticmethod @staticmethod
def open( def open(
@@ -275,7 +282,9 @@ class TxRequest(models.Model):
# Check balance # Check balance
source_balance = source.balance_of(source_sends) source_balance = source.balance_of(source_sends)
if source_balance < source_amount: if source_balance < source_amount:
raise BalanceError(source_balance, source, dest, source_amount, source_sends) raise BalanceError(
source_balance, source, dest, source_amount, source_sends
)
req = TxRequest.objects.create( req = TxRequest.objects.create(
status=TxRequest.OPEN, status=TxRequest.OPEN,
source=source, source=source,
@@ -288,20 +297,28 @@ class TxRequest(models.Model):
req.save() req.save()
return req return req
def can_accept(self) -> bool:
"""
Gets whether this request can be accepted.
"""
dest_balance = self.dest.balance_of(self.dest_sends)
return self.status == TxRequest.OPEN and dest_balance >= self.dest_amount
def accept(self): def accept(self):
""" """
Accepts an open transaction request. Accepts an open transaction request.
""" """
assert self.status == TxRequest.OPEN assert self.status == TxRequest.OPEN
# ensure destination balance before continuing # Ensure destination balance before continuing
if not self.can_accept():
dest_balance = self.dest.balance_of(self.dest_sends) dest_balance = self.dest.balance_of(self.dest_sends)
if dest_balance < self.dest_amount:
raise BalanceError( raise BalanceError(
dest_balance, self.dest, self.source, self.dest_amount, self.dest_sends dest_balance, self.dest, self.source, self.dest_amount, self.dest_sends
) )
# update status # Update status
self.status = TxRequest.ACCEPTED self.status = TxRequest.ACCEPTED
# Create source transaction # Create source transaction
@@ -336,6 +353,8 @@ class TxRequest(models.Model):
self.save() self.save()
class BalanceError(Exception): class BalanceError(Exception):
def __init__( def __init__(
self, balance: int, source: User, dest: User, amount: int, commodity: Commodity self, balance: int, source: User, dest: User, amount: int, commodity: Commodity
@@ -364,3 +383,11 @@ def _tx_pre_save(sender, instance, *args, **kwargs):
instance.amount, instance.amount,
instance.commodity, instance.commodity,
) )
@receiver(post_save, sender=TxRequest)
def __tx_post_save(sender, instance, created, **kwargs):
if not created:
return
assign_perm("view_txrequest", instance.source, instance)
assign_perm("view_txrequest", instance.dest, instance)

View File

@@ -7,6 +7,7 @@
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="{% static 'trading/css/bootstrap.min.css' %}" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"> <link rel="stylesheet" href="{% static 'trading/css/bootstrap.min.css' %}" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T">
<link rel="stylesheet" href="{% static 'trading/css/trading.css' %}">
<title>{% block title %}Trading{% if title %} - {{ title }}{% endif %}{% endblock title %}</title> <title>{% block title %}Trading{% if title %} - {{ title }}{% endif %}{% endblock title %}</title>
</head> </head>

View File

@@ -0,0 +1,42 @@
{% extends "trading/base.html" %}
{% load bootstrap4 %}
{% load humanize %}
{% block title %}
{% with title="Transaction request detail" %}
{{ block.super }}
{% endwith %}
{% endblock title %}
{% block content %}
<div class="row">
<div class="col-sm">
<table class="table">
<thead>
<tr>
<th>Commodity</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="{% url "trading:commodity_detail" object.source_sends.pk %}">{{ object.source_sends.name }}</a></td>
<td>
<span class="{% if object.source == request.user %}debit{% else %}credit{% endif %}">
{{object.source_amount|intcomma}}
</span>
</td>
</tr>
<tr>
<td><a href="{% url "trading:commodity_detail" object.dest_sends.pk %}">{{ object.dest_sends.name }}</a></td>
<td>
<span class="{% if object.dest == request.user %}debit{% else %}credit{% endif %}">
{{object.dest_amount|intcomma}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,34 @@
{% extends "trading/base.html" %}
{% load bootstrap4 %}
{% load humanize %}
{% block title %}
{% with title="Transaction detail" %}
{{ block.super }}
{% endwith %}
{% endblock title %}
{% block content %}
<div class="row">
<div class="col-sm">
<table class="table">
<thead>
<tr>
<th>Commodity</th>
<th>Delta</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="{% url "trading:commodity_detail" object.commodity.pk %}">{{ object.commodity.name }}</a></td>
<td>
<span class="{% if object.source == request.user %}debit{% else %}credit{% endif %}">
{{object.amount|intcomma}}
</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,43 @@
{% extends "trading/base.html" %}
{% load bootstrap4 %}
{% load humanize %}
{% block title %}
{% with title="Transaction detail" %}
{{ block.super }}
{% endwith %}
{% endblock title %}
{% block content %}
<div class="row">
<div class="col-sm">
<table class="table">
<thead>
<tr>
<th>Commodity</th>
<th>Delta</th>
<th>Status</th>
<th>Source</th>
<th>Destination</th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr>
<td><a href="{% url "trading:commodity_detail" object.commodity.pk %}">{{ object.commodity.name }}</a></td>
<td>
<span class="{% if object.source == request.user %}debit{% else %}credit{% endif %}">
{{object.amount|intcomma}}
</span>
</td>
<td>{{object.status}}</td>
<td>{{object.source.username}}</td>
<td>{{object.dest.username}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@@ -69,6 +69,9 @@ urlpatterns = [
path("u/profile/<int:pk>/", UserProfileView.as_view(), name="user_profile"), path("u/profile/<int:pk>/", UserProfileView.as_view(), name="user_profile"),
# t/ for tx # t/ for tx
path("t/detail/<str:pk>/", TxRequestDetailView.as_view(), name="tx_detail"),
path("t/", TxRequestListView.as_view(), name="tx_list"),
# c/ for commodities # c/ for commodities
path("c/create/", CommodityCreateView.as_view(), name="commodity_create"), path("c/create/", CommodityCreateView.as_view(), name="commodity_create"),

View File

@@ -1,22 +1,28 @@
from django import forms from django import forms
from django.contrib.auth import views as auth_views, password_validation from django.contrib.auth import views as auth_views, password_validation
from django.contrib.auth.forms import UsernameField from django.contrib.auth.forms import UsernameField
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db.models import Q
from django.http import HttpResponseForbidden, HttpResponseRedirect from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import CreateView, UpdateView from django.views.generic.edit import CreateView, UpdateView
from django.views.generic.list import ListView
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from trading.models import User, Commodity, MaxCommodityError from trading.models import User, Commodity, MaxCommodityError, Tx, TxRequest
class IndexView(LoginRequiredMixin, TemplateView): class IndexView(LoginRequiredMixin, TemplateView):
template_name = "trading/index.html" template_name = "trading/index.html"
################################################################################
# Commodity views
################################################################################
class CommodityDetailView(DetailView): class CommodityDetailView(DetailView):
template_name = "trading/c/detail.html" template_name = "trading/c/detail.html"
model = Commodity model = Commodity
@@ -43,6 +49,29 @@ class CommodityCreateView(LoginRequiredMixin, CreateView):
return super(CreateView, self).form_valid(form) return super(CreateView, self).form_valid(form)
################################################################################
# Tx views
################################################################################
class TxRequestDetailView(DetailView):
template_name = "trading/t/detail.html"
model = TxRequest
class TxRequestListView(LoginRequiredMixin, ListView):
template_name = "trading/t/list.html"
model = TxRequest
paginate_by = 100
def get_queryset(self):
user = self.request.user
return TxRequest.objects.filter(Q(source=user) | Q(dest=user)).order_by("-pk")
################################################################################
# User views
################################################################################
class UserProfileView(DetailView): class UserProfileView(DetailView):
template_name = "trading/u/profile.html" template_name = "trading/u/profile.html"
model = User model = User
@@ -78,3 +107,4 @@ class UserSettingsView(LoginRequiredMixin, UpdateView):
def get_object(self, queryset=None): def get_object(self, queryset=None):
return self.request.user return self.request.user