initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
db.sqlite3
|
||||
.venv
|
||||
static
|
||||
local.py
|
||||
settings_local.py
|
||||
__pycache__
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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 %}
|
||||
@@ -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')
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MonitorConfig(AppConfig):
|
||||
name = 'monitor'
|
||||
@@ -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)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -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 %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -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})
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RatesConfig(AppConfig):
|
||||
name = 'rates'
|
||||
@@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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"
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class TransactionsConfig(AppConfig):
|
||||
name = 'transactions'
|
||||
@@ -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',
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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"
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -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")
|
||||
@@ -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()
|
||||
@@ -0,0 +1,3 @@
|
||||
django
|
||||
django-registration
|
||||
|
||||
@@ -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()
|
||||
@@ -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 *
|
||||
@@ -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
|
||||
@@ -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),
|
||||
]
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user