initial commit

This commit is contained in:
2021-01-07 22:12:05 +01:00
commit e385650802
51 changed files with 1048 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
db.sqlite3
.venv
static
local.py
settings_local.py
__pycache__
+27
View File
@@ -0,0 +1,27 @@
Copyright (c) Timotheus Pokorra
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of BasxConnect nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+23
View File
@@ -0,0 +1,23 @@
https://github.com/dhg/Skeleton/blob/master/LICENSE.md
The MIT License (MIT)
Copyright (c) 2011-2014 Dave Gamache
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+17
View File
@@ -0,0 +1,17 @@
About
=====
This tool helps to watch your crypto portfolio.
You can see the current value of your wallet, and the current value of the crypto currencies you are watching.
You can manage your past transactions to and from your wallet.
This application is written in Python using Django.
Install
=======
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
+23
View File
@@ -0,0 +1,23 @@
{% load static %}
<html>
<head>
<title>Wallet Monitor</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% static 'Skeleton/css/normalize.css' %}">
<link rel="stylesheet" href="{% static 'Skeleton/css/skeleton.css' %}">
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'css/topnav.css' %}">
<link rel="stylesheet" href="{% static 'css/accordion.css' %}">
</head>
<body>
{% if user.is_authenticated %}
<nav class="topnav">
<a href="/monitor">Monitor</a>
<a href="/transactions/show">Transactions</a>
<a class="right" href="/accounts/logout/">Logout</a>
</nav>
{% endif %}
{% block content %}
{% endblock %}
</body>
</html>
@@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block content %}
<div class="form-box">
<h2>Success!</h2>
<p>Now you can <a href="{% url 'login' %}">Login</a></p>
</div>
{% endblock %}
@@ -0,0 +1,18 @@
Hello {{ user.username }},
you have registered at {{ site }}
If this was not you, please ignore this email!
If you want to activate your account at https://{{ site }}, please click on this link:
{% autoescape off %}
Please click on the link to confirm your registration, https://{{ site }}{% url 'django_registration_activate' activation_key=activation_key %}
{% endautoescape %}
All the best!
The team at {{ site }}
PS: this is an automated E-Mail, please do not reply.
You can contact us at info@{{ site }}
@@ -0,0 +1 @@
Confirm your registration for {{ site }}
@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block content %}
<div class="form-box">
<h2>Something went wrong.</h2>
<p>Perhaps you have confirmed already?</p>
<p>Please try to <a href="{% url 'login' %}">Login</a></p>
</div>
{% endblock %}
@@ -0,0 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="form-box">
<h2>Success!</h2>
<p>An E-Mail has been sent to. Please confirm by clicking on the link in that E-Mail!</p>
</div>
{% endblock %}
@@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block content %}
<div class="form-box">
{% if form.errors %}
<p>{{ form.errors }}</p>
{% endif %}
<form method="post" action="{% url 'django_registration_register' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.email.label_tag }}</td>
<td>{{ form.email }}</td>
</tr>
<tr>
<td>{{ form.password1.label_tag }}</td>
<td>{{ form.password1 }}</td>
</tr>
<tr>
<td>{{ form.password2.label_tag }}</td>
<td>{{ form.password2 }}</td>
</tr>
</table>
<input type="submit" value="Register" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
</div>
{% endblock %}
@@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block content %}
<div class="form-box">
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
{% if next %}
{% if user.is_authenticated %}
<p>Your account doesn't have access to this page. To proceed,
please login with an account that has access.</p>
{% else %}
<p>Please login to see this page.</p>
{% endif %}
{% endif %}
<form method="post" action="{% url 'login' %}">
{% csrf_token %}
<table>
<tr>
<td>{{ form.username.label_tag }}</td>
<td>{{ form.username }}</td>
</tr>
<tr>
<td>{{ form.password.label_tag }}</td>
<td>{{ form.password }}</td>
</tr>
</table>
<input type="submit" value="login" />
<input type="hidden" name="next" value="{{ next }}" />
</form>
{# Assumes you setup the password_reset view in your URLconf #}
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
<p><a href="{% url 'django_registration_register' %}">Create a free account</a></p>
</div>
{% endblock %}
+9
View File
@@ -0,0 +1,9 @@
from django.contrib.auth import authenticate
from django.shortcuts import render, redirect
def home(request):
# if not logged in => redirect to login screen
if not request.user.is_authenticated:
return redirect('/accounts/login/')
# if logged in => redirect to monitor view
return redirect('/monitor')
View File
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class MonitorConfig(AppConfig):
name = 'monitor'
+90
View File
@@ -0,0 +1,90 @@
from django.db import connection
from apps.transactions.models import Transaction
from apps.rates.models import ExchangeRate
from decimal import Decimal
import datetime
class Calc:
def ShowDiffRate(self, DayDiff, CurrentRate, Crypto, Fiat):
with connection.cursor() as cursor:
sql = """SELECT rate, datetime_valid FROM exchangerate WHERE crypto_currency = %s AND fiat_currency = %s
AND datetime_valid BETWEEN %s AND %s
ORDER BY datetime_valid DESC"""
startDate = datetime.datetime.now() - datetime.timedelta(days=int(DayDiff))
endDate = startDate + datetime.timedelta(days=1)
cursor.execute(sql, [Crypto, Fiat, startDate, endDate])
rateMinusXDay = cursor.fetchone()
if rateMinusXDay:
return {"dateRelative": "%s days ago" % (DayDiff,), "date" : rateMinusXDay[1], "rateEUR": rateMinusXDay[0],
"diffPercentage": (CurrentRate.rate-Decimal(rateMinusXDay[0]))/Decimal(rateMinusXDay[0])*100, "rateUSD": None}
return None
def GetCurrentValue(self, userid, crypto, total_investment, current_value, total_tax_free):
amount_kept = 0
out = {}
out["crypto"] = crypto
with connection.cursor() as cursor:
sql = """SELECT SUM(amount_after_fee) as after_fee, SUM(amount) as amount FROM `transaction` WHERE amount >= 0 and crypto_currency=%s and owner_id=%s"""
cursor.execute(sql, [crypto, userid])
bought = cursor.fetchone()
if bought[1]:
out["bought"] = bought[1]
total_investment += Decimal(bought[1])
amount_kept += Decimal(bought[0])
else:
out["bought"] = None
with connection.cursor() as cursor:
sql = """SELECT SUM(amount_before_fee) as before_fee, SUM(amount) as amount FROM `transaction` WHERE amount < 0 and crypto_currency=%s and owner_id=%s"""
cursor.execute(sql, [crypto, userid])
sold = cursor.fetchone()
if sold[1]:
out["sold"] = sold[1]
total_investment += Decimal(sold[1])
amount_kept += Decimal(sold[0])
else:
out["sold"] = None
rateEUR = ExchangeRate.objects.filter(crypto_currency=crypto, fiat_currency='EUR').order_by('-datetime_valid').first()
rateUSD = ExchangeRate.objects.filter(crypto_currency=crypto, fiat_currency='USD').order_by('-datetime_valid').first()
if rateEUR:
cv = Decimal(rateEUR.rate) * amount_kept
current_value += cv
out["current_value"] = cv
out["amount_kept"] = amount_kept
out["rateEUR"] = rateEUR.rate
else:
out["current_value"] = None
out["amount_kept"] = None
out["rateEUR"] = None
out["rates"] = []
if rateUSD and rateEUR:
out["rates"].append({"dateRelative": "Now", "date" : rateEUR.datetime_valid, "rateEUR": rateEUR.rate, "diffPercentage": None, "rateUSD": rateUSD.rate})
if rateEUR:
for daydiff in ["1", "3", "7", "14", "30"]:
diffrate = self.ShowDiffRate(daydiff, rateEUR, crypto, 'EUR')
if diffrate:
out["rates"].append(diffrate)
amount_within_past_year = 0
with connection.cursor() as cursor:
sql = """SELECT SUM(amount_after_fee) as amount FROM `transaction` WHERE amount > 0 and crypto_currency=%s and owner_id=%s and date_valid>%s"""
cursor.execute(sql, [crypto, userid, datetime.datetime.now() - datetime.timedelta(days=365)])
bought = cursor.fetchone()
out["bought_recently"] = None
if bought[0]:
out["bought_recently"] = bought[0]
amount_within_past_year = Decimal(bought[0])
amount_available_to_sell = amount_kept - amount_within_past_year
out["bought_tax_free"] = None
out["value_tax_free"] = None
if rateEUR:
out["bought_tax_free"] = amount_available_to_sell
out["value_tax_free"] = amount_available_to_sell * rateEUR.rate
total_tax_free += out["value_tax_free"]
return (total_investment, current_value, total_tax_free, rateEUR.rate, rateUSD.rate, out)
View File
+3
View File
@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.
+64
View File
@@ -0,0 +1,64 @@
{% extends 'base.html' %}
{% block content %}
<div class="form-box">
<h2>Monitor</h2>
<p>Hint: these numbers are not very accurate.</p>
{% for c in cryptos %}
<input type="checkbox" id="acc{{ c.crypto }}" />
<label for="acc{{ c.crypto }}">
{{ c.crypto }}
{% if c.rateEUR %}{{c.rateEUR|floatformat:0}} EUR{% endif %}
{% if c.rateUSD %}{{c.rateUSD|floatformat:0}} USD{% endif %}
</label>
<div class="content">
{% if c.bought %}
We have bought {{ c.crypto }} for {{ c.bought|floatformat:2 }} EUR<br/>
{% endif %}
{% if c.sold %}
We have sold {{ c.crypto }} for {{ c.sold|floatformat:2 }} EUR<br/>
{% endif %}
{% if c.amount_kept %}
Current value of our {{ c.amount_kept|floatformat:3 }} {{ c.crypto }} is {{ c.current_value|floatformat:2 }} EUR<br/>
{% endif %}
{% if c.bought_recently %}
We have bought within the last 12 months: {{ c.bought_recently|floatformat:3 }} {{ c.crypto }}<br/>
{% endif %}
{% if c.bought_tax_free %}
We can sell without paying taxes: {{ c.bought_tax_free|floatformat:3 }} {{ c.crypto }} for {{ c.value_tax_free|floatformat:2 }} EUR<br/>
{% endif %}
{% if c.rates %}
<table>
{% for r in c.rates %}
<tr>
<td>{{ r.dateRelative }}</td>
<td>{{ r.date|date:'Y-m-d' }}</td>
<td>{{ r.rateEUR|floatformat:0 }} EUR/{{ c.crypto }}
{% if r.rateUSD %}
<br/>
{{ r.rateUSD|floatformat:0 }} USD/{{ c.crypto }}
{% endif %}
</td>
<td>{% if r.diffPercentage %}
{{ r.diffPercentage|floatformat:1 }}%
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% endif %}
<br/><a href="/transactions/show?crypto={{ c.crypto }}">My transactions</a><br/>
<br/>
Google: <br/>
<a href='https://www.google.com/search?channel=crow2&client=firefox-b-d&q={{ c.crypto }}+in+eur%3D' target='_blank'>{{ c.crypto }} in EUR</a><br/>
<a href='https://www.google.com/search?channel=crow2&client=firefox-b-d&q={{ c.crypto }}+in+usd%3D' target='_blank'>{{ c.crypto }} in USD</a><br/>
</div>
{% endfor %}
</div>
{% endblock %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+22
View File
@@ -0,0 +1,22 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from apps.transactions.models import Transaction
from apps.rates.models import ExchangeRate
from apps.monitor.calc import Calc
@login_required
def monitor(request):
transactions = Transaction.objects.filter(owner=request.user)
crypto_currencies = {tr.crypto_currency for tr in transactions}
cryptos = []
total_investment = 0
current_value = 0
total_tax_free = 0
calc = Calc()
for crypto in crypto_currencies:
(total_investment, current_value, total_tax_free, rateEUR, rateUSD, out2) = calc.GetCurrentValue(request.user.id, crypto, total_investment, current_value, total_tax_free)
cryptos.append({"crypto": crypto, "rateEUR": rateEUR, "rateUSD": rateUSD, **out2})
return render(request,"monitor.html",{'cryptos':cryptos})
View File
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class RatesConfig(AppConfig):
name = 'rates'
+27
View File
@@ -0,0 +1,27 @@
# Generated by Django 3.1.4 on 2021-01-03 21:17
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='ExchangeRate',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('crypto_currency', models.CharField(max_length=10)),
('fiat_currency', models.CharField(max_length=10)),
('rate', models.DecimalField(decimal_places=10, max_digits=24)),
('datetime_valid', models.DateTimeField(verbose_name='datetime valid')),
],
options={
'db_table': 'exchangerate',
},
),
]
View File
+10
View File
@@ -0,0 +1,10 @@
from django.db import models
# Create your models here.
class ExchangeRate(models.Model):
crypto_currency = models.CharField(max_length=10)
fiat_currency = models.CharField(max_length=10)
rate = models.DecimalField(max_digits=24, decimal_places=10)
datetime_valid = models.DateTimeField('datetime valid')
class Meta:
db_table = "exchangerate"
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+3
View File
@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.
View File
+3
View File
@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.
+5
View File
@@ -0,0 +1,5 @@
from django.apps import AppConfig
class TransactionsConfig(AppConfig):
name = 'transactions'
+14
View File
@@ -0,0 +1,14 @@
from django import forms
from apps.transactions.models import Transaction
#from datetimepicker.widgets import DateTimePicker
#from django.contrib.admin.widgets import AdminDateWidget
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = "__all__"
# https://stackoverflow.com/a/52702275/1632368
#date_valid = forms.DateField(widget=AdminDateWidget())
@@ -0,0 +1,34 @@
# Generated by Django 3.1.4 on 2020-12-26 10:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Transaction',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('crypto_currency', models.CharField(max_length=10)),
('amount_before_fee', models.DecimalField(decimal_places=10, max_digits=24)),
('amount_after_fee', models.DecimalField(decimal_places=10, max_digits=24)),
('exchange_rate', models.DecimalField(decimal_places=10, max_digits=24)),
('fiat_currency', models.CharField(max_length=10)),
('amount', models.DecimalField(decimal_places=10, max_digits=24)),
('date_valid', models.DateTimeField(verbose_name='date transfered')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'transaction',
},
),
]
+15
View File
@@ -0,0 +1,15 @@
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class Transaction(models.Model):
crypto_currency = models.CharField(max_length=10)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
amount_before_fee = models.DecimalField(max_digits=24, decimal_places=10)
amount_after_fee = models.DecimalField(max_digits=24, decimal_places=10)
exchange_rate = models.DecimalField(max_digits=24, decimal_places=10)
fiat_currency = models.CharField(max_length=10)
amount = models.DecimalField(max_digits=24, decimal_places=10)
date_valid = models.DateTimeField('date transfered')
class Meta:
db_table = "transaction"
+90
View File
@@ -0,0 +1,90 @@
{% extends 'base.html' %}
{% block content %}
<div class="form-box">
<form method="POST" class="post-form" action="/transactions/add">
{% csrf_token %}
<div class="container">
<br>
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">
<strong>{{field.name}}: {{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
<div class="form-group row">
<label class="col-sm-1 col-form-label"></label>
<div class="col-sm-4">
<h3>Add transaction</h3>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Crypto Currency:</label>
<div class="col-sm-4">
{{ form.crypto_currency }}
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Amount before fee:</label>
<div class="col-sm-4">
{{ form.amount_before_fee }}
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Amount after fee:</label>
<div class="col-sm-4">
{{ form.amount_after_fee }}
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Exchange Rate:</label>
<div class="col-sm-4">
{{ form.exchange_rate }}
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Fiat Currency:</label>
<div class="col-sm-4">
{{ form.fiat_currency }}
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Fiat Amount:</label>
<div class="col-sm-4">
{{ form.amount }}
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Date valid:</label>
<div class="col-sm-4">
{{ form.date_valid }}
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label"></label>
<div class="col-sm-4">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
+90
View File
@@ -0,0 +1,90 @@
{% extends 'base.html' %}
{% block content %}
<div class="form-box">
<form method="POST" class="post-form" action="/transactions/update/{{transaction.id}}">
{% csrf_token %}
<div class="container">
<br>
{% if form.errors %}
{% for field in form %}
{% for error in field.errors %}
<div class="alert alert-danger">
<strong>{{field.name}}: {{ error|escape }}</strong>
</div>
{% endfor %}
{% endfor %}
{% for error in form.non_field_errors %}
<div class="alert alert-danger">
<strong>{{ error|escape }}</strong>
</div>
{% endfor %}
{% endif %}
<div class="form-group row">
<label class="col-sm-1 col-form-label"></label>
<div class="col-sm-4">
<h3>Update Transaction</h3>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Crypto Currency:</label>
<div class="col-sm-4">
<input type="text" name="crypto_currency" id="id_crypto_currency" required maxlength="10" value="{{ transaction.crypto_currency }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Amount before fee:</label>
<div class="col-sm-4">
<input type="decimal" name="amount_before_fee" id="id_amount_before_fee" required value="{{ transaction.amount_before_fee }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Amount after fee:</label>
<div class="col-sm-4">
<input type="decimal" name="amount_after_fee" id="id_amount_after_fee" required value="{{ transaction.amount_after_fee }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Exchange Rate:</label>
<div class="col-sm-4">
<input type="decimal" name="exchange_rate" id="id_exchange_rate" required value="{{ transaction.exchange_rate }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Fiat Currency:</label>
<div class="col-sm-4">
<input type="text" name="fiat_currency" id="id_fiat_currency" required maxlength="10" value="{{ transaction.fiat_currency }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Fiat Amount:</label>
<div class="col-sm-4">
<input type="decimal" name="amount" id="id_amount" required value="{{ transaction.amount }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-2 col-form-label">Date valid:</label>
<div class="col-sm-4">
<input type="text" name="date_valid" id="id_date_valid" required value="{{ transaction.date_valid|date:'Y-m-d H:i:s' }}"/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-1 col-form-label"></label>
<div class="col-sm-4">
<button type="submit" class="btn btn-success">Update</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
+41
View File
@@ -0,0 +1,41 @@
{% extends 'base.html' %}
{% load humanize %}
{% block content %}
<div class="form-box">
<center><a href="/transactions/add" class="button button-primary">Add New Record</a></center>
<br/>
<table class="table table-striped table-bordered table-sm">
<thead class="thead-dark">
<tr>
<th>Date</th>
<th>Amount</th>
<th>Value</th>
<th colspan="2">Rate</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for transaction in transactions %}
<tr>
<td>{{ transaction.date_valid }}</td>
<td>{{ transaction.amount_before_fee|floatformat:3 }} {{ transaction.crypto_currency }}</td>
<td>{{ transaction.amount|floatformat:2 }} {{ transaction.fiat_currency }}</td>
<td>{% if transaction.amount < 0 %}
{{ transaction.exchange_rate|floatformat:0 }} {{ transaction.crypto_currency }}/{{ transaction.fiat_currency }}
{% endif %}
</td>
<td>{% if transaction.amount > 0 %}
{{ transaction.exchange_rate|floatformat:0 }} {{ transaction.crypto_currency }}/{{ transaction.fiat_currency }}
{% endif %}
</td>
<td>
<a href="/transactions/edit/{{ transaction.id }}"><span class="glyphicon glyphicon-pencil" >Edit</span></a>
<a href="/transactions/delete/{{ transaction.id }}" onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
+3
View File
@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.
+52
View File
@@ -0,0 +1,52 @@
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from apps.transactions.forms import TransactionForm
from apps.transactions.models import Transaction
@login_required
def add(request):
if request.method == "POST":
# request.POST is immutable, so make a copy
values = request.POST.copy()
values['owner'] = request.user.id
form = TransactionForm(values)
if form.is_valid():
try:
form.save()
return redirect('/transactions/show')
except:
pass
else:
form = TransactionForm()
return render(request,'add.html',{'form':form})
@login_required
def show(request):
if 'crypto' in request.GET:
transactions = Transaction.objects.filter(owner=request.user, crypto_currency=request.GET['crypto']).order_by('-date_valid')
else:
transactions = Transaction.objects.filter(owner=request.user).order_by('-date_valid')
return render(request,"show.html",{'transactions':transactions})
@login_required
def edit(request, id):
transaction = Transaction.objects.get(id=id, owner=request.user)
return render(request,'edit.html', {'transaction':transaction})
@login_required
def update(request, id):
transaction = Transaction.objects.get(id=id, owner=request.user)
# request.POST is immutable, so make a copy
values = request.POST.copy()
values['owner'] = request.user.id
form = TransactionForm(values, instance = transaction)
if form.is_valid():
form.save()
return redirect("/transactions/show")
return render(request, 'edit.html', {'transaction': transaction, 'form': form})
@login_required
def destroy(request, id):
transaction = Transaction.objects.get(id=id, owner=request.user.id)
transaction.delete()
return redirect("/transactions/show")
Executable
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'walletmonitor.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
+3
View File
@@ -0,0 +1,3 @@
django
django-registration
View File
+16
View File
@@ -0,0 +1,16 @@
"""
ASGI config for walletmonitor project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'walletmonitor.settings')
application = get_asgi_application()
+126
View File
@@ -0,0 +1,126 @@
"""
Django settings for walletmonitor project.
Generated by 'django-admin startproject' using Django 3.1.4.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django.contrib.sites',
'django_registration',
'apps.core',
'apps.rates',
'apps.transactions',
'apps.monitor',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'walletmonitor.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'walletmonitor.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [ '/static/' ]
LOGIN_REDIRECT_URL = '/home/'
LOGOUT_REDIRECT_URL = '/home/'
from .settings_local import *
+16
View File
@@ -0,0 +1,16 @@
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ACCOUNT_ACTIVATION_DAYS = 1
REGISTRATION_OPEN = True
DEFAULT_FROM_EMAIL = 'no-reply@example.org'
EMAIL_HOST = 'smtp.example.org'
EMAIL_PORT = 25
EMAIL_HOST_USER = 'myuser'
EMAIL_HOST_PASSWORD = 'topsecret'
EMAIL_USE_TLS = True
SITE_ID = 1
+35
View File
@@ -0,0 +1,35 @@
"""walletmonitor URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
from apps.transactions import views as tr_views
from apps.core import views as core_views
from apps.monitor import views as monitor_views
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django_registration.backends.activation.urls')),
path('accounts/', include('django.contrib.auth.urls')),
path('', core_views.home),
path('home/', core_views.home),
path('monitor/', monitor_views.monitor),
path('transactions/add', tr_views.add),
path('transactions/show', tr_views.show),
path('transactions/edit/<int:id>', tr_views.edit),
path('transactions/update/<int:id>', tr_views.update),
path('transactions/delete/<int:id>', tr_views.destroy),
]
+16
View File
@@ -0,0 +1,16 @@
"""
WSGI config for walletmonitor project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'walletmonitor.settings')
application = get_wsgi_application()