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,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.db import migrations, models
|
||||
@@ -58,12 +58,12 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('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)),
|
||||
('source_amount', models.PositiveIntegerField()),
|
||||
('dest_amount', models.PositiveIntegerField()),
|
||||
('dest', models.ForeignKey(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')),
|
||||
('source', models.ForeignKey(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_amount', models.PositiveIntegerField(editable=False)),
|
||||
('dest_amount', models.PositiveIntegerField(editable=False)),
|
||||
('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(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='trading.Commodity')),
|
||||
('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(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='trading.Commodity')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
|
||||
@@ -7,6 +7,7 @@ from django.db.models.signals import post_save, pre_save
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from frozendict import frozendict
|
||||
from guardian.shortcuts import assign_perm
|
||||
from hashid_field import HashidAutoField
|
||||
from trading.managers import UserManager
|
||||
|
||||
@@ -89,9 +90,9 @@ class User(PermissionsMixin, AbstractBaseUser):
|
||||
}
|
||||
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"))
|
||||
for row in self.transaction_requests_sent.filter(status=TxRequest.OPEN)
|
||||
.values("source_sends")
|
||||
.annotate(Sum("source_amount"))
|
||||
}
|
||||
keys = (
|
||||
set(ipos.keys())
|
||||
@@ -244,21 +245,27 @@ class TxRequest(models.Model):
|
||||
# )
|
||||
|
||||
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(
|
||||
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(
|
||||
Commodity, on_delete=models.CASCADE, related_name="+", null=True,
|
||||
Commodity, on_delete=models.CASCADE, related_name="+", null=True, editable=False,
|
||||
)
|
||||
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()
|
||||
dest_amount = models.PositiveIntegerField()
|
||||
source_amount = models.PositiveIntegerField(editable=False)
|
||||
dest_amount = models.PositiveIntegerField(editable=False)
|
||||
|
||||
@staticmethod
|
||||
def open(
|
||||
@@ -275,7 +282,9 @@ class TxRequest(models.Model):
|
||||
# Check balance
|
||||
source_balance = source.balance_of(source_sends)
|
||||
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(
|
||||
status=TxRequest.OPEN,
|
||||
source=source,
|
||||
@@ -288,20 +297,28 @@ class TxRequest(models.Model):
|
||||
req.save()
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
# Ensure destination balance before continuing
|
||||
if not self.can_accept():
|
||||
dest_balance = self.dest.balance_of(self.dest_sends)
|
||||
raise BalanceError(
|
||||
dest_balance, self.dest, self.source, self.dest_amount, self.dest_sends
|
||||
)
|
||||
|
||||
# update status
|
||||
# Update status
|
||||
self.status = TxRequest.ACCEPTED
|
||||
|
||||
# Create source transaction
|
||||
@@ -336,6 +353,8 @@ class TxRequest(models.Model):
|
||||
self.save()
|
||||
|
||||
|
||||
|
||||
|
||||
class BalanceError(Exception):
|
||||
def __init__(
|
||||
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.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 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/trading.css' %}">
|
||||
<title>{% block title %}Trading{% if title %} - {{ title }}{% endif %}{% endblock title %}</title>
|
||||
</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"),
|
||||
|
||||
# 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
|
||||
path("c/create/", CommodityCreateView.as_view(), name="commodity_create"),
|
||||
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
from django import forms
|
||||
from django.contrib.auth import views as auth_views, password_validation
|
||||
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.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseForbidden, HttpResponseRedirect
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic.detail import DetailView
|
||||
from django.views.generic.edit import CreateView, UpdateView
|
||||
from django.views.generic.list import ListView
|
||||
from django.urls import reverse, reverse_lazy
|
||||
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):
|
||||
template_name = "trading/index.html"
|
||||
|
||||
|
||||
################################################################################
|
||||
# Commodity views
|
||||
################################################################################
|
||||
|
||||
class CommodityDetailView(DetailView):
|
||||
template_name = "trading/c/detail.html"
|
||||
model = Commodity
|
||||
@@ -43,6 +49,29 @@ class CommodityCreateView(LoginRequiredMixin, CreateView):
|
||||
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):
|
||||
template_name = "trading/u/profile.html"
|
||||
model = User
|
||||
@@ -78,3 +107,4 @@ class UserSettingsView(LoginRequiredMixin, UpdateView):
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
return self.request.user
|
||||
|
||||
|
||||
Reference in New Issue
Block a user