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:
1
Pipfile
1
Pipfile
@@ -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
127
Pipfile.lock
generated
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
17
static/trading/css/trading.css
Normal file
17
static/trading/css/trading.css
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.debit {
|
||||||
|
color: red;
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.debit:before {
|
||||||
|
content: "(";
|
||||||
|
}
|
||||||
|
|
||||||
|
.debit:after {
|
||||||
|
content: ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
.credit {
|
||||||
|
color: black;
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
}
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
42
trading/templates/trading/r/detail.html
Normal file
42
trading/templates/trading/r/detail.html
Normal 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 %}
|
||||||
34
trading/templates/trading/t/detail.html
Normal file
34
trading/templates/trading/t/detail.html
Normal 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 %}
|
||||||
43
trading/templates/trading/t/list.html
Normal file
43
trading/templates/trading/t/list.html
Normal 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 %}
|
||||||
|
|
||||||
@@ -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"),
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user