Skip to content

Commit

Permalink
Major changes
Browse files Browse the repository at this point in the history
This commit includes a lot of changes, including:
Major UI/UX change, base template is changed
Most of pages have been reworked
Now it is possible to create new stores and wallets
without loading pages, just via popup.
Data is now represented in good-looking datatables(datatables.js)
which support paging, searching by any of columns of data,
and some other features.
In wallets page, simply press on wallet in the table on the left
to see transactions of this wallet in the table on the right
Another big change is support for two factor authentification.
Note that after some changes in our login templates it may not work
properly, submit an issue if it doesn't work. It is possible to
authentificate using any authentificator app like google
authentificator, or using backup codes generated.
Now we have changed to postgres database for better performance.
We have changed our structure, if before it was:
user->store->product
So now it is:
user->wallet->store->product
May look strange that you will need an extra step, but this provides
more flexibility. For example, you can create a few stores with
different products(and domains), linking to the same wallet, so no
need to input some settings for each store and syncronyze each store.
Now your wallet is starting to sync right after you create it, if you
don't leave the wallets page after creating wallet, in some time you
will see a notification in top right corner of screen saying that wallet
has been syncronyzed. If xpub was invalid, it will say about it
instantly.
Also a lot of other fixes and things have been fixed, performance was
improved.
  • Loading branch information
MrNaif2018 committed Mar 18, 2019
1 parent 145ac4c commit e709e9a
Show file tree
Hide file tree
Showing 89 changed files with 55,951 additions and 31,013 deletions.
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,12 @@ __pycache__/
.vscode/
#pytest
pytest.ini
#backups
backups/
backup/
#locales,for now
locales/
#logs
debug.log
#migrations will be generated on client machines
migrations/
1 change: 0 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
MIT License

Copyright (c) 2017-2019 btcpayserver
Copyright (c) 2019 MrNaif2018

Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down
15 changes: 12 additions & 3 deletions daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,19 +39,25 @@ async def get_tx_async(tx):
result_formatted=Transaction(result).deserialize()
result_formatted.update({"confirmations":result["confirmations"]})
return result_formatted

def exchange_rate():
return fx.exchange_rate()

wallets={}
supported_methods={"get_transaction":get_transaction}
supported_methods={"get_transaction":get_transaction, "exchange_rate":exchange_rate}

#verbosity level, uncomment for debug info
#set_verbosity(True)

def start_it():
global network
global network, fx
asyncio.set_event_loop(asyncio.new_event_loop())
config = SimpleConfig()
config.set_key("currency","USD")
config.set_key("use_exchange_rate", True)
daemon = Daemon(config, listen_jsonrpc=False)
network = daemon.network
fx=daemon.fx
while True:
pass

Expand Down Expand Up @@ -92,7 +98,10 @@ async def xpub_func(request):
params=data.get("params",[])
if not method:
return web.json_response({"jsonrpc": "2.0", "error": {"code": -32601, "message": "Procedure not found."}, "id": id})
wallet=load_wallet(xpub)
try:
wallet=load_wallet(xpub)
except Exception:
return web.json_response({"jsonrpc": "2.0", "error": {"code": -32601, "message": "Error loading wallet"}, "id": id})
if method in supported_methods:
exec_method=supported_methods[method]
else:
Expand Down
19 changes: 17 additions & 2 deletions gui/admin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from django.utils.translation import ugettext_lazy as _
from . import models

# Register your models here.
class CustomUserAdmin(UserAdmin):
model = models.User
app_label="authentication"
list_display = ['username','email','first_name','last_name','is_staff','is_confirmed']
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser','is_confirmed',
'groups', 'user_permissions')}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)

# Register your models here.
admin.site.register(models.User, CustomUserAdmin)
admin.site.register(models.Store)
admin.site.register(models.Product)
admin.site.register(models.Product)
admin.site.register(models.Wallet)
7 changes: 7 additions & 0 deletions gui/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,11 @@ class ProductViewSet(viewsets.ModelViewSet):

queryset = models.Product.objects.all()
serializer_class = serializers.ProductSerializer
permission_classes = [permissions.IsAuthenticated]

class WalletViewSet(viewsets.ModelViewSet):
"""ViewSet for the Product class"""

queryset = models.Wallet.objects.all()
serializer_class = serializers.WalletSerializer
permission_classes = [permissions.IsAuthenticated]
33 changes: 32 additions & 1 deletion gui/consumers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
from django.shortcuts import render,get_object_or_404,redirect,reverse
import urllib
from . import models
import json
import time

class ChatConsumer(WebsocketConsumer):
class InvoiceConsumer(WebsocketConsumer):
def connect(self):
self.invoice_id=self.scope["url_route"]["kwargs"]["invoice"]
self.invoice=get_object_or_404(models.Product,id=self.invoice_id)
Expand All @@ -32,3 +33,33 @@ def notify(self, message):
self.send(text_data=json.dumps({
'status': message["status"]
}))

class WalletConsumer(WebsocketConsumer):
def connect(self):
try:
self.wallet_id=urllib.parse.parse_qs(self.scope['query_string'].decode()).get("wallet", (None,))[0]
except (KeyError, IndexError):
self.close()
self.wallet=get_object_or_404(models.Wallet,id=self.wallet_id)
async_to_sync(self.channel_layer.group_add)(self.wallet_id, self.channel_name)
self.accept()

def disconnect(self, close_code):
async_to_sync(self.channel_layer.group_discard)(self.wallet_id, self.channel_name)

def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']

self.send(text_data=json.dumps({
'message': message
}))

def notify(self, message):
"""message={'status':'paid', balance:0.1}"""
if 'status' not in message.keys():
raise ValueError('message must include an status key')
self.send(text_data=json.dumps({
'status': message["status"],
"balance":message["balance"]
}))
35 changes: 35 additions & 0 deletions gui/context_processors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#pylint: disable=no-member
from . import models
from django.conf import settings
from django.db.models import Sum
from django.utils import timezone
from bitcart.coins.btc import BTC

RPC_USER=settings.RPC_USER
RPC_PASS=settings.RPC_PASS

RPC_URL=settings.RPC_URL

btc=BTC(RPC_URL)

def provide_stats(request):
if request.user.is_authenticated:
products=models.Product.objects.filter(store__wallet__user=request.user)
products=products.order_by("-date")
products_count=len(products)
stores_count=models.Store.objects.filter(wallet__user=request.user).count()
wallets=models.Wallet.objects.filter(user=request.user)
wallets_count=len(wallets)
wallets_balance=0
for i in wallets:
if timezone.now() - i.updated_date >= timezone.timedelta(hours=2):
wallets_balance+=float(BTC(RPC_URL,xpub=i.xpub, rpc_user=RPC_USER, rpc_pass=RPC_PASS).balance()['confirmed'])
i.updated_date=timezone.now()
i.save()
else:
wallets_balance+=i.balance
wallets_balance=format(wallets_balance,".08f").rstrip("0").rstrip(".")
return {"products":products, "stores_count":stores_count, "wallets_count":wallets_count,
"products_count":products_count, "wallets_balance":wallets_balance}
else:
return {}
31 changes: 26 additions & 5 deletions gui/forms.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
from django import forms
from django.contrib.auth.models import User
from django.conf import settings
from . import models

class StoreForm(forms.ModelForm):

class Meta:
model = models.Store
fields = ("id","name","website","can_invoice","xpub","invoice_expire","fee_mode","payment_tolerance")
fields = ("name","wallet","domain","template","email")

def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
visible.field.widget.attrs['class'] = 'form-control'
self.fields["can_invoice"].widget.attrs['class'] = 'form-check'
self.fields["id"].widget.attrs["readonly"]=True
#self.fields["wallet"].widget.attrs['class'] = 'form-check'
#self.fields["id"].widget.attrs["readonly"]=True

class UpdateStoreForm(forms.ModelForm):

class Meta:
model = models.Store
fields = ("name","wallet","domain","template","email")

def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
visible.field.widget.attrs['class'] = 'form-control'
#visible.field.required=False
#print(self.visible_fields())
#self.fields["wallet"].widget.attrs['class'] = 'form-check'
#self.fields["id"].widget.attrs["readonly"]=True

class ProductForm(forms.ModelForm):

Expand All @@ -27,7 +42,7 @@ class CreateStoreForm(forms.Form):
class RegisterForm(forms.ModelForm):
confirm_password = forms.CharField(max_length=255,widget=forms.PasswordInput())
class Meta:
model = User
model = models.User
fields = ("username","email","password")
widgets={
"confirm_password":forms.PasswordInput(),
Expand Down Expand Up @@ -68,3 +83,9 @@ def __init__(self,*args, **kwargs):
super().__init__(*args, **kwargs)
for visible in self.visible_fields():
visible.field.widget.attrs['class'] = 'form-control'

class WalletForm(forms.ModelForm):

class Meta:
model = models.Wallet
fields = ("name","xpub")
92 changes: 84 additions & 8 deletions gui/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,108 @@
# Generated by Django 2.1.7 on 2019-02-17 17:00
# Generated by Django 2.1.7 on 2019-03-17 14:01

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import embed_video.fields


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]

operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('is_confirmed', models.BooleanField(blank=True, default=False)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='Product',
fields=[
('id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('amount', models.FloatField()),
('quantity', models.FloatField()),
('title', models.CharField(max_length=1000)),
('status', models.CharField(default='new', max_length=1000)),
('order_id', models.CharField(blank=True, default='', max_length=255)),
('date', models.DateTimeField(default=django.utils.timezone.now)),
('description', models.TextField(blank=True, null=True)),
('image', models.ImageField(blank=True, null=True, upload_to='')),
('video', embed_video.fields.EmbedVideoField(blank=True, null=True)),
('bitcoin_address', models.CharField(default='', max_length=255)),
('bitcoin_url', models.CharField(default='', max_length=255)),
],
options={
'db_table': 'products',
'managed': True,
},
),
migrations.CreateModel(
name='Store',
fields=[
('id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('can_delete', models.IntegerField()),
('name', models.CharField(max_length=1000)),
('website', models.CharField(max_length=1000)),
('can_invoice', models.BooleanField(default=False)),
('xpub', models.CharField(max_length=1000)),
('invoice_expire', models.IntegerField(default=15)),
('fee_mode', models.IntegerField(choices=[(1, '... only if the customer makes more than one payment for the invoice'), (2, 'Always'), (3, 'Never')], default=1)),
('payment_tolerance', models.FloatField(default=0)),
('domain', models.CharField(blank=True, default='', max_length=1000)),
('template', models.CharField(blank=True, default='', max_length=1000)),
('email', models.CharField(blank=True, default='', max_length=1000)),
],
options={
'db_table': 'stores',
'managed': True,
},
),
migrations.CreateModel(
name='Wallet',
fields=[
('id', models.CharField(max_length=255, primary_key=True, serialize=False)),
('name', models.CharField(max_length=1000)),
('xpub', models.CharField(blank=True, default='', max_length=1000)),
('balance', models.FloatField(blank=True, default=0)),
('updated_date', models.DateTimeField(default=django.utils.timezone.now)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'wallets',
'managed': True,
},
),
migrations.AddField(
model_name='store',
name='wallet',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gui.Wallet'),
),
migrations.AddField(
model_name='product',
name='store',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gui.Store'),
),
]
23 changes: 0 additions & 23 deletions gui/migrations/0002_auto_20190217_1710.py

This file was deleted.

Loading

0 comments on commit e709e9a

Please sign in to comment.