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

@@ -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(

View File

@@ -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)

View File

@@ -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>

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"),
# 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"),

View File

@@ -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