From 08e081bead33a5ecd01ed34deb688156f05da3d2 Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 5 May 2023 23:15:03 +0300 Subject: [PATCH 1/4] vendor --- base/admin.py | 5 +- base/models.py | 17 ++++--- base/templates/base/vendor.html | 36 +++++++------- base/urls.py | 2 + base/views.py | 84 +++++++++++++++++++++++++++++++-- dLitepay/settings.py | 6 +-- templates/partials/base.html | 2 + 7 files changed, 119 insertions(+), 33 deletions(-) diff --git a/base/admin.py b/base/admin.py index 34ef61e..200c187 100644 --- a/base/admin.py +++ b/base/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin -# Register your models here. \ No newline at end of file +# Register your models here. +from .models import VendorsData + +admin.site.register(VendorsData) \ No newline at end of file diff --git a/base/models.py b/base/models.py index 4defafe..30b865f 100644 --- a/base/models.py +++ b/base/models.py @@ -1,23 +1,26 @@ from django.db import models from users.models import User +from django.contrib import admin # Create your models here. class VendorsData(models.Model): + id = models.BigAutoField(primary_key=True) vendorid = models.ForeignKey(User, default=None, on_delete=models.CASCADE) vendorUUID = models.UUIDField() vendor = models.CharField(max_length=8, unique=True) vendorSecretKey = models.UUIDField() vendorCoverAmount = models.IntegerField(default=0) - vendorPaidNotification = models.IntegerField(default=1) - vendorSkipScreen = models.IntegerField(default=0) - vendorPayWindow = models.IntegerField(default=0) + vendorPaidNotification = models.BooleanField(default=True) + vendorSkipScreen = models.BooleanField(default=False) + vendorPayWindow = models.BooleanField(default=True) vendorWebName = models.CharField(max_length=250) - vendorWebAddr = models.CharField(max_length=250) + vendorWebAddr = models.URLField(max_length=250) vendorAddDate = models.DateTimeField(auto_now_add=True) - vendorDeleted = models.IntegerField(default=0) - vendorAddDelete = models.DateTimeField(auto_now=True) - vendorIsActive = models.IntegerField(default=1) + vendorDeleted = models.BooleanField(default=False) + vendorUpdated = models.DateTimeField(auto_now=True) + vendorAddDelete = models.DateTimeField(auto_now_add=True) + vendorIsActive = models.BooleanField(default=True) def __str__(self): return self.vendor diff --git a/base/templates/base/vendor.html b/base/templates/base/vendor.html index f5ad1d9..41b49f9 100644 --- a/base/templates/base/vendor.html +++ b/base/templates/base/vendor.html @@ -1,6 +1,6 @@ {% extends "partials/base.html" %} {% load static %} -{% block title %}Dashboard{% endblock title %} +{% block title %}Vendor Page{% endblock title %} {% block content %} @@ -16,7 +16,6 @@

Vendor Page

- @@ -39,9 +38,9 @@ VendorID - Creation Date Website Name - + Created + Updated Status @@ -50,22 +49,18 @@ - ... - {{vendor.vendor}} - - {{vendor.vendorAddDate}} - - - ... - - {{vendor.vendorWebName}} - + {{vendor.vendorWebName}} + {{vendor.vendorAddDate}} + + + + {{vendor.vendorUpdated|timesince}} ago {% if vendor.vendorIsActive == 0 %} @@ -79,8 +74,8 @@ {% endif %} - Edit - Addresses + Edit + @@ -90,7 +85,14 @@ diff --git a/base/urls.py b/base/urls.py index f83b06d..ba06898 100644 --- a/base/urls.py +++ b/base/urls.py @@ -8,4 +8,6 @@ urlpatterns = [ path('', views.index, name="dashboard"), path('vendor/', views.VendorPage, name="vendor"), + path('vendor/edit//', views.VendorEditPage, name="edit-vendor"), + path('vendor/create', views.VendorCreatePage, name="create-vendor"), ] \ No newline at end of file diff --git a/base/views.py b/base/views.py index 43fefbd..8e8822d 100644 --- a/base/views.py +++ b/base/views.py @@ -1,10 +1,15 @@ from django.shortcuts import render, redirect from django.contrib import messages -from users.models import User -from .models import VendorsData from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required - +from django.core.exceptions import ValidationError +from django.db.models import Q +# +from users.models import User +from .models import VendorsData +from .forms import VendorEditForm +from .functions import vendor_generator +import uuid # Create your views here. @login_required(login_url='login') @@ -41,6 +46,75 @@ def LogoutPage(request): @login_required(login_url='login') def VendorPage(request): - vendors = VendorsData.objects.filter(vendorid_id=request.user.id) + vendors = VendorsData.objects.filter(Q(vendorid_id=request.user.id) & Q(vendorDeleted=False)) context = {'vendors': vendors} - return render(request, 'base/vendor.html', context) \ No newline at end of file + return render(request, 'base/vendor.html', context) + +@login_required(login_url='login') +def VendorEditPage(request, vdr ): + vendor = VendorsData.objects.get(vendorUUID=vdr) + try: + if vendor.vendorid_id is not request.user.id: + messages.error(request, "You are not authorized to edit this vendor") + return redirect('vendor') + except Exception as e: + messages.error(request, "Something wrong happened, try again.") + return redirect('vendor') + + + form = VendorEditForm() + vendor_title = "Edit Vendor" + if request.method == 'POST': + if request.POST.get('submit') == 'Submit': + try: + vendors = VendorsData.objects.get(vendorUUID=vdr) + form = VendorEditForm(request.POST, instance=vendors) + if form.is_valid(): + form.save() + messages.success(request, "Vendor information saved") + return redirect('vendor') + else: + messages.error(request, "Something wrong happened, try again.") + except Exception as e: + messages.error(request, "Something wrong happened, try again.") + vendors = VendorsData.objects.get(vendorUUID=vdr) + context = {'vendors': vendors, 'form': form, 'vendor_title': vendor_title} + return render(request, 'base/vendorEdit.html', context) + +@login_required(login_url='login') +def VendorCreatePage(request): + vendor_title = "Create Vendor" + if request.method == 'POST': + try: + form = VendorEditForm(request.POST) + if form.is_valid(): + # Generate vendor + new_vendor = form.save(commit=False) + try: + vdr = vendor_generator() + vendor_check = VendorsData.objects.get(vendorUUID=vdr) + while vendor_check is vdr: + vdr = vendor_generator() + vendor_check = VendorsData.objects.get(vendorUUID=vdr) + except: + pass + + new_vendor.vendor = vdr + new_vendor.vendorSecretKey = uuid.uuid4() + new_vendor.vendorUUID = uuid.uuid4() + new_vendor.vendorid_id = request.user.id + # Save vendor + try: + new_vendor.save() + messages.success(request, "Vendor created") + return redirect('vendor') + except Exception as e: + print(e) + else: + messages.error(request, "Something wrong happened, try again.") + except Exception as e: + messages.error(request, "Something wrong happened, try again.") + + + context = {'vendor_title': vendor_title} + return render(request, 'base/vendorEdit.html', context) \ No newline at end of file diff --git a/dLitepay/settings.py b/dLitepay/settings.py index 69f47b7..73c75b7 100644 --- a/dLitepay/settings.py +++ b/dLitepay/settings.py @@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-z#_ppfgs06)e4v18t!970-&-&jkxht!tw&ms#u5n_m-fbxulwa # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['10.0.70.5'] # User substitution # https://docs.djangoproject.com/en/1.11/topics/auth/customizing/#auth-custom-user @@ -113,13 +113,13 @@ AUTH_PASSWORD_VALIDATORS = [ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] - +''' PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', ] - +''' # Internationalization # https://docs.djangoproject.com/en/4.2/topics/i18n/ diff --git a/templates/partials/base.html b/templates/partials/base.html index 75d3431..c2afdba 100755 --- a/templates/partials/base.html +++ b/templates/partials/base.html @@ -14,6 +14,8 @@ + + {% endblock css %} From e348d39de58936a7fccb082913422756f85f9ad2 Mon Sep 17 00:00:00 2001 From: Bob Date: Fri, 5 May 2023 23:16:08 +0300 Subject: [PATCH 2/4] vendor --- base/forms.py | 7 + base/functions.py | 5 + .../0005_alter_vendorsdata_vendorisactive.py | 18 ++ ...lter_vendorsdata_vendordeleted_and_more.py | 33 ++++ ...0007_vendorsdata_vendorupdated_and_more.py | 23 +++ .../0008_alter_vendorsdata_vendorwebaddr.py | 18 ++ base/migrations/0009_alter_vendorsdata_id.py | 18 ++ base/templates/base/vendorEdit.html | 159 ++++++++++++++++++ 8 files changed, 281 insertions(+) create mode 100644 base/forms.py create mode 100644 base/functions.py create mode 100644 base/migrations/0005_alter_vendorsdata_vendorisactive.py create mode 100644 base/migrations/0006_alter_vendorsdata_vendordeleted_and_more.py create mode 100644 base/migrations/0007_vendorsdata_vendorupdated_and_more.py create mode 100644 base/migrations/0008_alter_vendorsdata_vendorwebaddr.py create mode 100644 base/migrations/0009_alter_vendorsdata_id.py create mode 100644 base/templates/base/vendorEdit.html diff --git a/base/forms.py b/base/forms.py new file mode 100644 index 0000000..1a4ce1c --- /dev/null +++ b/base/forms.py @@ -0,0 +1,7 @@ +from django.forms import ModelForm +from .models import VendorsData + +class VendorEditForm(ModelForm): + class Meta: + model = VendorsData + fields = ['vendorCoverAmount', 'vendorPaidNotification', 'vendorSkipScreen', 'vendorPayWindow', 'vendorWebName', 'vendorWebAddr'] \ No newline at end of file diff --git a/base/functions.py b/base/functions.py new file mode 100644 index 0000000..dd284c7 --- /dev/null +++ b/base/functions.py @@ -0,0 +1,5 @@ +import string +import random + +def vendor_generator(size=6, chars=string.ascii_uppercase + string.digits): + return ''.join(random.choice(chars) for _ in range(size)) \ No newline at end of file diff --git a/base/migrations/0005_alter_vendorsdata_vendorisactive.py b/base/migrations/0005_alter_vendorsdata_vendorisactive.py new file mode 100644 index 0000000..eda3647 --- /dev/null +++ b/base/migrations/0005_alter_vendorsdata_vendorisactive.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2023-04-29 14:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0004_vendorsdata_vendoruuid'), + ] + + operations = [ + migrations.AlterField( + model_name='vendorsdata', + name='vendorIsActive', + field=models.BooleanField(default=1), + ), + ] diff --git a/base/migrations/0006_alter_vendorsdata_vendordeleted_and_more.py b/base/migrations/0006_alter_vendorsdata_vendordeleted_and_more.py new file mode 100644 index 0000000..0abf473 --- /dev/null +++ b/base/migrations/0006_alter_vendorsdata_vendordeleted_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.2 on 2023-04-29 14:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0005_alter_vendorsdata_vendorisactive'), + ] + + operations = [ + migrations.AlterField( + model_name='vendorsdata', + name='vendorDeleted', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='vendorsdata', + name='vendorPaidNotification', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='vendorsdata', + name='vendorPayWindow', + field=models.BooleanField(default=True), + ), + migrations.AlterField( + model_name='vendorsdata', + name='vendorSkipScreen', + field=models.BooleanField(default=False), + ), + ] diff --git a/base/migrations/0007_vendorsdata_vendorupdated_and_more.py b/base/migrations/0007_vendorsdata_vendorupdated_and_more.py new file mode 100644 index 0000000..0cc85d7 --- /dev/null +++ b/base/migrations/0007_vendorsdata_vendorupdated_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2023-04-29 17:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0006_alter_vendorsdata_vendordeleted_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='vendorsdata', + name='vendorUpdated', + field=models.DateTimeField(auto_now=True), + ), + migrations.AlterField( + model_name='vendorsdata', + name='vendorAddDelete', + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/base/migrations/0008_alter_vendorsdata_vendorwebaddr.py b/base/migrations/0008_alter_vendorsdata_vendorwebaddr.py new file mode 100644 index 0000000..3815b02 --- /dev/null +++ b/base/migrations/0008_alter_vendorsdata_vendorwebaddr.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2023-05-02 12:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0007_vendorsdata_vendorupdated_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='vendorsdata', + name='vendorWebAddr', + field=models.URLField(max_length=250), + ), + ] diff --git a/base/migrations/0009_alter_vendorsdata_id.py b/base/migrations/0009_alter_vendorsdata_id.py new file mode 100644 index 0000000..3f52bd4 --- /dev/null +++ b/base/migrations/0009_alter_vendorsdata_id.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2 on 2023-05-02 15:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0008_alter_vendorsdata_vendorwebaddr'), + ] + + operations = [ + migrations.AlterField( + model_name='vendorsdata', + name='id', + field=models.BigAutoField(primary_key=True, serialize=False), + ), + ] diff --git a/base/templates/base/vendorEdit.html b/base/templates/base/vendorEdit.html new file mode 100644 index 0000000..1dc1218 --- /dev/null +++ b/base/templates/base/vendorEdit.html @@ -0,0 +1,159 @@ +{% extends "partials/base.html" %} +{% load static %} +{% block title %}Vendor Edit Page{% endblock title %} +{% block content %} + + + + + + +
+ +
+
+
+
+
+ +

Edit page

+ + +
+ + +
+ + +
+
+
+ +
+
+ + + +
+
+
{% block vendor_title %} {{vendor_title}} {% endblock vendor_title %}
+
+
+ + +
+ {% csrf_token %} + + {{ form.non_field_errors }} +
+ +
+
+
+ +
+ +
+ Shown on invoice page + {% if form.vendorWebName.errors %} + {% for error in form.vendorWebName.errors %} + {{ error|escape }} + {% endfor %} + {% endif %} +
+
+
+ +
+
+
+ +
+ +
+ Shown on invoice page +
+
+
+ +
+ + Skip the How-to screen on invoice page? +
+
+
+ +
+ + Get notifid by email for each payment received? +
+
+
+ +
+ + Amount covered by you in case the buyer sends less than required +
+
+
+ +
+ + When should we send you your payments? +
+
+
+
+ +
+
+
+
+ + {% if messages %} +
    + {% for message in messages %} + {{ message }}

    + {% endfor %} +
+ {% endif %} +
+
+
+
+ + + +
+ {% block footer %} + {% include "partials/footer.html" %} + {% endblock footer %} +
+ + + + +{% endblock content %} \ No newline at end of file From 2b4d8bb4f3f00fc5f32b78a7e45d1352f7e9c183 Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 30 May 2023 12:28:55 +0300 Subject: [PATCH 3/4] added vendor files --- dLitepay/settings.py | 45 ++++++++++++++++++++++++++++++++++++-------- dLitepay/urls.py | 10 ++++++++-- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/dLitepay/settings.py b/dLitepay/settings.py index 73c75b7..3d42183 100644 --- a/dLitepay/settings.py +++ b/dLitepay/settings.py @@ -26,7 +26,7 @@ SECRET_KEY = 'django-insecure-z#_ppfgs06)e4v18t!970-&-&jkxht!tw&ms#u5n_m-fbxulwa # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['10.0.70.5'] +ALLOWED_HOSTS = ['10.0.70.5', '127.0.0.1'] # User substitution # https://docs.djangoproject.com/en/1.11/topics/auth/customizing/#auth-custom-user @@ -43,6 +43,13 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_bcrypt', + 'django_otp', + 'django_otp.plugins.otp_static', + 'django_otp.plugins.otp_totp', + 'django_otp.plugins.otp_email', # <- if you want email capability. + 'two_factor', + 'two_factor.plugins.phonenumber', # <- if you want phone number capability. + 'two_factor.plugins.email', # <- if you want email capability. # 'base.apps.BaseConfig', 'users.apps.UsersConfig', @@ -54,6 +61,7 @@ MIDDLEWARE = [ 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django_otp.middleware.OTPMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] @@ -143,23 +151,44 @@ STATICFILES_DIRS = [ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + }, + }, + 'loggers': { + 'two_factor': { + 'handlers': ['console'], + 'level': 'INFO', + } + } +} + # Messages customize MESSAGE_TAGS = { - messages.DEBUG: "alert-info", - messages.INFO: "alert-info", - messages.SUCCESS: "alert-success", - messages.WARNING: "alert-warning", - messages.ERROR: "alert-danger", + messages.DEBUG: "text-info", + messages.INFO: "text-info", + messages.SUCCESS: "text-success", + messages.WARNING: "text-warning", + messages.ERROR: "text-danger", } - BCRYPT_ROUNDS = 11 PASSWORD_RESET_TIMEOUT = 3600 # +#LOGIN_URL = 'two_factor:login' +#OTP_LOGIN_URL = 'account:login' SESSION_EXPIRE_AT_BROWSER_CLOSE = True SESSION_COOKIE_SECURE = False #to be switched to true when in prod CSRF_COOKIE_SECURE = False #to be switched to true when in prod SECURE_SSL_REDIRECT = False #to be switched to true when in prod SESSION_COOKIE_AGE = 3600 -SERVER_EMAIL = 'info@litepay.ch' \ No newline at end of file +TWO_FACTOR_REMEMBER_COOKIE_AGE = 3600 +SERVER_EMAIL = 'info@litepay.ch' +DEFAULT_FROM_EMAIL = 'info@litepay.ch' \ No newline at end of file diff --git a/dLitepay/urls.py b/dLitepay/urls.py index 9738c13..5c97f9d 100644 --- a/dLitepay/urls.py +++ b/dLitepay/urls.py @@ -16,9 +16,15 @@ Including another URLconf """ from django.contrib import admin from django.urls import path, include +from two_factor.urls import urlpatterns as tf_urls +urlpatterns = [ + #path('', include(tf_urls)), + path('', include('base.urls')) +] - +''' urlpatterns = [ path('admin/', admin.site.urls), - path('', include('base.urls')) + ] +''' \ No newline at end of file From 7324707b44ad0072e9bcd4901e8e096de35a4f13 Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 30 May 2023 12:30:00 +0300 Subject: [PATCH 4/4] new files --- base/admin.py | 16 +- base/forms.py | 9 +- base/functions.py | 630 ++++++++++++++++++++++++++- base/models.py | 47 +- base/templates/base/dashboard.html | 640 +++++++++++++++------------- base/templates/base/vendor.html | 40 +- base/templates/base/vendorEdit.html | 203 +++++---- base/urls.py | 5 + base/views.py | 142 ++++-- src/.DS_Store | Bin 8196 -> 6148 bytes templates/main.html | 4 +- templates/partials/base.html | 40 +- templates/partials/footer.html | 3 +- templates/partials/sidebar.html | 331 ++++++++++---- templates/partials/topbar.html | 10 +- users/models.py | 1 + 16 files changed, 1533 insertions(+), 588 deletions(-) diff --git a/base/admin.py b/base/admin.py index 200c187..db6864e 100644 --- a/base/admin.py +++ b/base/admin.py @@ -1,6 +1,18 @@ from django.contrib import admin # Register your models here. -from .models import VendorsData +from .models import VendorsData, CryptoCoins, VendorsAddresses -admin.site.register(VendorsData) \ No newline at end of file +class VendorDetails(admin.ModelAdmin): + fields = ["vendor"] + list_display = ("vendorid_id", "vendor", "vendorWebName", "vendorWebAddr", "vendorAddDate", "vendorIsActive") + +class CryptoDetails(admin.ModelAdmin): + list_display = ("coinName", "coinSymbol", "coinAddDate", "coinIsActive" ) + +class VendorAddressDetails(admin.ModelAdmin): + list_display = ("coin", "address", "vendor_id", "vendorid_id", "addrIsActive") + +admin.site.register(VendorsData, VendorDetails) +admin.site.register(CryptoCoins, CryptoDetails) +admin.site.register(VendorsAddresses, VendorAddressDetails) \ No newline at end of file diff --git a/base/forms.py b/base/forms.py index 1a4ce1c..9a45957 100644 --- a/base/forms.py +++ b/base/forms.py @@ -1,7 +1,12 @@ from django.forms import ModelForm -from .models import VendorsData +from .models import VendorsData, VendorsAddresses class VendorEditForm(ModelForm): class Meta: model = VendorsData - fields = ['vendorCoverAmount', 'vendorPaidNotification', 'vendorSkipScreen', 'vendorPayWindow', 'vendorWebName', 'vendorWebAddr'] \ No newline at end of file + fields = ['vendorCoverAmount', 'vendorPaidNotification', 'vendorPayWindow', 'vendorWebName', 'vendorWebAddr', 'vendorNetworkFee'] + +class VendorAddrAddForm(ModelForm): + class Meta: + model = VendorsAddresses + fields = ['coin', 'address'] \ No newline at end of file diff --git a/base/functions.py b/base/functions.py index dd284c7..ef5b68a 100644 --- a/base/functions.py +++ b/base/functions.py @@ -1,5 +1,629 @@ -import string -import random +import string, hashlib, binascii, random, socket, base58, sys +from typing import Union +from urllib.parse import urlparse +from ipaddress import ip_address, ip_network, IPv4Address +from base58 import b58decode_check, b58encode_check +from enum import Enum def vendor_generator(size=6, chars=string.ascii_uppercase + string.digits): - return ''.join(random.choice(chars) for _ in range(size)) \ No newline at end of file + return ''.join(random.choice(chars) for _ in range(size)) + + +def decodeBase58(address): + decoded = base58.b58decode(address).hex() + prefixAndHash = decoded[:len(decoded)-8] + checksum = decoded[len(decoded)-8:] + hash = prefixAndHash + for _ in range(1,3): + hash = hashlib.sha256(binascii.unhexlify(hash)).hexdigest() + if(checksum == hash[:8]): + return True + return False + +def decodeMonero(address): + # decode monero address + if len(address) == 95 or len(address) == 106: + # Decode the address from Base58 + decoded = decode(address) + + # Verify address type byte + if decoded[0:2] not in ["12", "2a", "13"]: + return False + + # Verify checksum + # later + return True + return False + +def checksumCheck(method, address): + match method.lower(): + case 'btc': + return decodeBase58(address) if address[0] == '1' or address[0] == '3' else True if address[0:3] == 'bc1' and decode("bc", address)[0] != None else False + case 'btct': + return decodeBase58(address) if address[0] == '2' else True if address[0:3] == 'tb1' and decode("tb", address)[0] != None else False + case 'ltc': + return decodeBase58(address) if address[0] == '3' or address[0] == 'M' or address[0] == 'L' else True if address[0:4] == 'ltc1' and decode("ltc", address)[0] != None else False + case 'bch': + return is_valid(address) if address[0] == '1' else True if is_valid('bitcoincash:'+address) == True else False + case 'zec': + return decodeBase58(address) if address[0] == 't' or address[0] == 'z' else False + case 'xmr': + #needs new function to check if address is valid, a decoder maybe + return decodeMonero(address) + case _: + return False + +######################################################################### +## UrlValidator +######################################################################### + +class UrlValidator: + @staticmethod + def is_internal_address(ip: Union[IPv4Address]) -> bool: + return any([ + ip.is_private, + ip.is_unspecified, + ip.is_reserved, + ip.is_loopback, + ip.is_multicast, + ip.is_link_local, + ]) + + @classmethod + def validate(cls, url: str): + DEFAULT_PORT_WHITELIST = {80, 81, 8080, 443, 8443, 8000} + DEFAULT_SCHEME_WHITELIST = {'http', 'https'} + DEFAULT_HOST_BLACKLIST = {'192.0.0.192', '169.254.169.254', '100.100.100.200', 'metadata.packet.net', 'metadata.google.internal'} + DEFAULT_CHARACTER_WHITELIST = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:/-_.?&=' + + if url is None: + return False + + whitelist_set = set(DEFAULT_CHARACTER_WHITELIST) + if any(c not in whitelist_set for c in url): + return False + + try: + ip = ip_address(url) + except ValueError: + try: + host = urlparse(url).hostname + ip = ip_address(str(socket.gethostbyname(host))) + except: + return False + + port_whitelist = DEFAULT_PORT_WHITELIST.copy() + scheme_whitelist = DEFAULT_SCHEME_WHITELIST.copy() + host_blacklist = DEFAULT_HOST_BLACKLIST.copy() + + try: + port, scheme = urlparse(url).port, urlparse(url).scheme + except: + return False + + if scheme_whitelist and scheme is not None and scheme not in scheme_whitelist: + return False + + if host_blacklist and host is not None and host in host_blacklist: + return False + + if port_whitelist and port is not None and port not in port_whitelist: + return False + + if ip.version == 4: + if not ip.is_private: + # CGNAT IPs do not set `is_private` so `not is_global` added + if not ip_network(ip).is_global: + return False + else: + return False + + if cls.is_internal_address(ip): + return False + + return True + + +"""Reference implementation for Bech32/Bech32m and segwit addresses.""" +######################################################################### +## SEGWIT +######################################################################### + +class Encoding(Enum): + """Enumeration type to list the various supported encodings.""" + BECH32 = 1 + BECH32M = 2 + +CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" +BECH32M_CONST = 0x2bc830a3 + +def bech32_polymod(values): + """Internal function that computes the Bech32 checksum.""" + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + chk = 1 + for value in values: + top = chk >> 25 + chk = (chk & 0x1ffffff) << 5 ^ value + for i in range(5): + chk ^= generator[i] if ((top >> i) & 1) else 0 + return chk + + +def bech32_hrp_expand(hrp): + """Expand the HRP into values for checksum computation.""" + return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp] + + +def bech32_verify_checksum(hrp, data): + """Verify a checksum given HRP and converted data characters.""" + const = bech32_polymod(bech32_hrp_expand(hrp) + data) + if const == 1: + return Encoding.BECH32 + if const == BECH32M_CONST: + return Encoding.BECH32M + return None + +def bech32_create_checksum(hrp, data, spec): + """Compute the checksum values given HRP and data.""" + values = bech32_hrp_expand(hrp) + data + const = BECH32M_CONST if spec == Encoding.BECH32M else 1 + polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ const + return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)] + + +def bech32_encode(hrp, data, spec): + """Compute a Bech32 string given HRP and data values.""" + combined = data + bech32_create_checksum(hrp, data, spec) + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) + +def bech32_decode(bech): + """Validate a Bech32/Bech32m string, and determine HRP and data.""" + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): + return (None, None, None) + bech = bech.lower() + pos = bech.rfind('1') + if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: + return (None, None, None) + if not all(x in CHARSET for x in bech[pos+1:]): + return (None, None, None) + hrp = bech[:pos] + data = [CHARSET.find(x) for x in bech[pos+1:]] + spec = bech32_verify_checksum(hrp, data) + if spec is None: + return (None, None, None) + return (hrp, data[:-6], spec) + +def convertbits(data, frombits, tobits, pad=True): + """General power-of-2 base conversion.""" + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +def decode(hrp, addr): + """Decode a segwit address.""" + hrpgot, data, spec = bech32_decode(addr) + if hrpgot != hrp: + return (None, None) + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 2 or len(decoded) > 40: + return (None, None) + if data[0] > 16: + return (None, None) + if data[0] == 0 and len(decoded) != 20 and len(decoded) != 32: + return (None, None) + if data[0] == 0 and spec != Encoding.BECH32 or data[0] != 0 and spec != Encoding.BECH32M: + return (None, None) + return (data[0], decoded) + + +def encode(hrp, witver, witprog): + """Encode a segwit address.""" + spec = Encoding.BECH32 if witver == 0 else Encoding.BECH32M + ret = bech32_encode(hrp, [witver] + convertbits(witprog, 8, 5), spec) + if decode(hrp, ret) == (None, None): + return None + return ret + +######################################################################### +## CRYPTO +######################################################################### +CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' + +def polymod(values): + chk = 1 + generator = [ + (0x01, 0x98f2bc8e61), + (0x02, 0x79b76d99e2), + (0x04, 0xf33e5fb3c4), + (0x08, 0xae2eabe2a8), + (0x10, 0x1e4f43e470)] + for value in values: + top = chk >> 35 + chk = ((chk & 0x07ffffffff) << 5) ^ value + for i in generator: + if top & i[0] != 0: + chk ^= i[1] + return chk ^ 1 + + +def prefix_expand(prefix): + return [ord(x) & 0x1f for x in prefix] + [0] + + +def calculate_checksum(prefix, payload): + poly = polymod(prefix_expand(prefix) + payload + [0, 0, 0, 0, 0, 0, 0, 0]) + out = list() + for i in range(8): + out.append((poly >> 5 * (7 - i)) & 0x1f) + return out + + +def verify_checksum(prefix, payload): + return polymod(prefix_expand(prefix) + payload) == 0 + + +def b32decode(inputs): + out = list() + for letter in inputs: + out.append(CHARSET.find(letter)) + return out + + +def b32encode(inputs): + out = '' + for char_code in inputs: + out += CHARSET[char_code] + return out + + +def convertbits(data, frombits, tobits, pad=True): + acc = 0 + bits = 0 + ret = [] + maxv = (1 << tobits) - 1 + max_acc = (1 << (frombits + tobits - 1)) - 1 + for value in data: + if value < 0 or (value >> frombits): + return None + acc = ((acc << frombits) | value) & max_acc + bits += frombits + while bits >= tobits: + bits -= tobits + ret.append((acc >> bits) & maxv) + if pad: + if bits: + ret.append((acc << (tobits - bits)) & maxv) + elif bits >= frombits or ((acc << (tobits - bits)) & maxv): + return None + return ret + + +######################################################################### +## BCH CONVERT +######################################################################### +class InvalidAddress(Exception): + pass + + +class Address: + VERSION_MAP = { + 'legacy': [ + ('P2SH', 5, False), + ('P2PKH', 0, False), + ('P2SH-TESTNET', 196, True), + ('P2PKH-TESTNET', 111, True) + ], + 'cash': [ + ('P2SH', 8, False), + ('P2PKH', 0, False), + ('P2SH-TESTNET', 8, True), + ('P2PKH-TESTNET', 0, True) + ] + } + MAINNET_PREFIX = 'bitcoincash' + TESTNET_PREFIX = 'bchtest' + + def __init__(self, version, payload, prefix=None): + self.version = version + self.payload = payload + if prefix: + self.prefix = prefix + else: + if Address._address_type('cash', self.version)[2]: + self.prefix = self.TESTNET_PREFIX + else: + self.prefix = self.MAINNET_PREFIX + + def __str__(self): + return 'version: {}\npayload: {}\nprefix: {}'.format(self.version, self.payload, self.prefix) + + def legacy_address(self): + version_int = Address._address_type('legacy', self.version)[1] + return b58encode_check(Address.code_list_to_string([version_int] + self.payload)) + + def cash_address(self): + version_int = Address._address_type('cash', self.version)[1] + payload = [version_int] + self.payload + payload = convertbits(payload, 8, 5) + checksum = calculate_checksum(self.prefix, payload) + return self.prefix + ':' + b32encode(payload + checksum) + + @staticmethod + def code_list_to_string(code_list): + if sys.version_info > (3, 0): + output = bytes() + for code in code_list: + output += bytes([code]) + else: + output = '' + for code in code_list: + output += chr(code) + return output + + @staticmethod + def _address_type(address_type, version): + for mapping in Address.VERSION_MAP[address_type]: + if mapping[0] == version or mapping[1] == version: + return mapping + raise InvalidAddress('Could not determine address version') + + @staticmethod + def from_string(address_string): + try: + address_string = str(address_string) + except Exception: + raise InvalidAddress('Expected string as input') + if ':' not in address_string: + return Address._legacy_string(address_string) + else: + return Address._cash_string(address_string) + + @staticmethod + def _legacy_string(address_string): + try: + decoded = bytearray(b58decode_check(address_string)) + except ValueError: + raise InvalidAddress('Could not decode legacy address') + version = Address._address_type('legacy', decoded[0])[0] + payload = list() + for letter in decoded[1:]: + payload.append(letter) + return Address(version, payload) + + @staticmethod + def _cash_string(address_string): + if address_string.upper() != address_string and address_string.lower() != address_string: + raise InvalidAddress('Cash address contains uppercase and lowercase characters') + address_string = address_string.lower() + colon_count = address_string.count(':') + if colon_count == 0: + address_string = Address.MAINNET_PREFIX + ':' + address_string + elif colon_count > 1: + raise InvalidAddress('Cash address contains more than one colon character') + prefix, base32string = address_string.split(':') + decoded = b32decode(base32string) + if not verify_checksum(prefix, decoded): + raise InvalidAddress('Bad cash address checksum') + converted = convertbits(decoded, 5, 8) + version = Address._address_type('cash', converted[0])[0] + if prefix == Address.TESTNET_PREFIX: + version += '-TESTNET' + payload = converted[1:-6] + return Address(version, payload, prefix) + + +def to_cash_address(address): + return Address.from_string(address).cash_address() + + +def to_legacy_address(address): + return Address.from_string(address).legacy_address() + + +def is_valid(address): + try: + Address.from_string(address) + return True + except InvalidAddress: + return False + +######################################################################### +## monero +######################################################################### + + +__alphabet = [ + ord(s) for s in "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +] +__b58base = 58 +__UINT64MAX = 2 ** 64 +__encodedBlockSizes = [0, 2, 3, 5, 6, 7, 9, 10, 11] +__fullBlockSize = 8 +__fullEncodedBlockSize = 11 + + +def _hexToBin(hex_): + if len(hex_) % 2 != 0: + raise ValueError("Hex string has invalid length: %d" % len(hex_)) + return [int(hex_[i : i + 2], 16) for i in range(0, len(hex_), 2)] + + +def _binToHex(bin_): + return "".join("%02x" % int(b) for b in bin_) + + +def _uint8be_to_64(data): + if not (1 <= len(data) <= 8): + raise ValueError("Invalid input length: %d" % len(data)) + + res = 0 + for b in data: + res = res << 8 | b + return res + + +def _uint64_to_8be(num, size): + if size < 1 or size > 8: + raise ValueError("Invalid input length: %d" % size) + res = [0] * size + + twopow8 = 2 ** 8 + for i in range(size - 1, -1, -1): + res[i] = num % twopow8 + num = num // twopow8 + + return res + + +def encode_block(data, buf, index): + l_data = len(data) + + if l_data < 1 or l_data > __fullEncodedBlockSize: + raise ValueError("Invalid block length: %d" % l_data) + + num = _uint8be_to_64(data) + i = __encodedBlockSizes[l_data] - 1 + + while num > 0: + remainder = num % __b58base + num = num // __b58base + buf[index + i] = __alphabet[remainder] + i -= 1 + + return buf + + +def encode(hex): + """Encode hexadecimal string as base58 (ex: encoding a Monero address).""" + data = _hexToBin(hex) + l_data = len(data) + + if l_data == 0: + return "" + + full_block_count = l_data // __fullBlockSize + last_block_size = l_data % __fullBlockSize + res_size = ( + full_block_count * __fullEncodedBlockSize + __encodedBlockSizes[last_block_size] + ) + + res = bytearray([__alphabet[0]] * res_size) + + for i in range(full_block_count): + res = encode_block( + data[(i * __fullBlockSize) : (i * __fullBlockSize + __fullBlockSize)], + res, + i * __fullEncodedBlockSize, + ) + + if last_block_size > 0: + res = encode_block( + data[ + (full_block_count * __fullBlockSize) : ( + full_block_count * __fullBlockSize + last_block_size + ) + ], + res, + full_block_count * __fullEncodedBlockSize, + ) + + return bytes(res).decode("ascii") + + +def decode_block(data, buf, index): + l_data = len(data) + + if l_data < 1 or l_data > __fullEncodedBlockSize: + raise ValueError("Invalid block length: %d" % l_data) + + res_size = __encodedBlockSizes.index(l_data) + if res_size <= 0: + raise ValueError("Invalid block size: %d" % res_size) + + res_num = 0 + order = 1 + for i in range(l_data - 1, -1, -1): + digit = __alphabet.index(data[i]) + if digit < 0: + raise ValueError("Invalid symbol: %s" % data[i]) + + product = order * digit + res_num + if product > __UINT64MAX: + raise ValueError( + "Overflow: %d * %d + %d = %d" % (order, digit, res_num, product) + ) + + res_num = product + order = order * __b58base + + if res_size < __fullBlockSize and 2 ** (8 * res_size) <= res_num: + raise ValueError("Overflow: %d doesn't fit in %d bit(s)" % (res_num, res_size)) + + tmp_buf = _uint64_to_8be(res_num, res_size) + buf[index : index + len(tmp_buf)] = tmp_buf + + return buf + + +def decode(enc): + """Decode a base58 string (ex: a Monero address) into hexidecimal form.""" + enc = bytearray(enc, encoding="ascii") + l_enc = len(enc) + + if l_enc == 0: + return "" + + full_block_count = l_enc // __fullEncodedBlockSize + last_block_size = l_enc % __fullEncodedBlockSize + try: + last_block_decoded_size = __encodedBlockSizes.index(last_block_size) + except ValueError: + raise ValueError("Invalid encoded length: %d" % l_enc) + + data_size = full_block_count * __fullBlockSize + last_block_decoded_size + + data = bytearray(data_size) + for i in range(full_block_count): + data = decode_block( + enc[ + (i * __fullEncodedBlockSize) : ( + i * __fullEncodedBlockSize + __fullEncodedBlockSize + ) + ], + data, + i * __fullBlockSize, + ) + + if last_block_size > 0: + data = decode_block( + enc[ + (full_block_count * __fullEncodedBlockSize) : ( + full_block_count * __fullEncodedBlockSize + last_block_size + ) + ], + data, + full_block_count * __fullBlockSize, + ) + + return _binToHex(data) + +######################################################################### +######################################################################### \ No newline at end of file diff --git a/base/models.py b/base/models.py index 30b865f..e8106a7 100644 --- a/base/models.py +++ b/base/models.py @@ -4,13 +4,30 @@ from django.contrib import admin # Create your models here. + +class CryptoCoins(models.Model): + id = models.BigAutoField(primary_key=True) + coinName = models.CharField(max_length=250) + coinSymbol = models.CharField(max_length=10) + coinIsActive = models.BooleanField(default=True) + coinAddDate = models.DateTimeField(auto_now_add=True) + coinUpdated = models.DateTimeField(auto_now=True) + coinDelete = models.DateTimeField(auto_now_add=True, blank=True, null=True) + coinDeleted = models.BooleanField(default=False) + + class Meta: + verbose_name = "Crypto Coin" + verbose_name_plural = "Crypto Coins" + ordering = ["-id"] + class VendorsData(models.Model): id = models.BigAutoField(primary_key=True) vendorid = models.ForeignKey(User, default=None, on_delete=models.CASCADE) vendorUUID = models.UUIDField() vendor = models.CharField(max_length=8, unique=True) - vendorSecretKey = models.UUIDField() + vendorSecretKey = models.CharField(max_length=250) vendorCoverAmount = models.IntegerField(default=0) + vendorNetworkFee = models.IntegerField(default=0) vendorPaidNotification = models.BooleanField(default=True) vendorSkipScreen = models.BooleanField(default=False) vendorPayWindow = models.BooleanField(default=True) @@ -24,10 +41,34 @@ class VendorsData(models.Model): def __str__(self): return self.vendor + + def __repr__(self): + return self._repr() class Meta: verbose_name = "Vendor" verbose_name_plural = "Vendors" ordering = ["-id"] - - \ No newline at end of file + +class VendorsAddresses(models.Model): + id = models.BigAutoField(primary_key=True) + vendor = models.ForeignKey(VendorsData, default=None, on_delete=models.CASCADE) + vendorid = models.ForeignKey(User, default=None, on_delete=models.CASCADE) + vendorUUID = models.UUIDField() + coin = models.CharField(max_length=10) + address = models.CharField(max_length=250) + addrAddDate = models.DateTimeField(auto_now_add=True) + addrDeleted = models.BooleanField(default=False) + addrUpdated = models.DateTimeField(auto_now=True) + addrIsActive = models.BooleanField(default=True) + + def __str__(self): + return self.address + + class Meta: + verbose_name = "Address" + verbose_name_plural = "Addresses" + ordering = ["-addrAddDate"] + + + diff --git a/base/templates/base/dashboard.html b/base/templates/base/dashboard.html index 1ff54b3..b66ef5f 100644 --- a/base/templates/base/dashboard.html +++ b/base/templates/base/dashboard.html @@ -7,321 +7,349 @@
- -
-
-
-
-
- -

Dashboard

- -

Hello "user", welcome! Here's what's happening with your store today.

-
- - -
- - -
-
-
+ -
-
- -
-
-
-
-
-
- Earnings - $750.90 -
-
-
- -
-
-
-
- - 13% - - Since last month -
-
-
-
-
-
-
-
-
- Balance - 215 -
-
-
- -
-
-
-
- - 30% - - Since last month -
-
-
-
-
-
-
-
-
- Customers - 1.400 -
-
-
- -
-
-
-
- - -5% - - Since last month -
-
-
-
-
-
-
-
-
- Invoices - 150 -
-
-
- -
-
-
-
- - 10% - - Since last month -
-
-
-
+
+
+
+
+
+

Dashboard

+
+
- - - -
-
-
Last 5 paid invoices
+
+ +
+
+
+
+
+
+
+
+ ... +
USDT
+
+
+ Total balance +
+
+ 75.800,00 USDT +
+
+
+
+
+
+
+
+ ... + BTC +
+
+ 3.2893 USDT +
+
+ + + + +13.7% +
+
+
+
+
+
+
+
+ ... + ADA +
+
+ 10.745,49 ADA +
+
+ + + + -3.2% +
+
+
+
+
+
+
+
+ ... + EOS +
+
+ 7.890,00 EOS +
+
+ + + + -2.2% +
+
+
+
+
+
+ + + +
+
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameDateCompanyOfferMeeting
- ... - - Robert Fox - - - Feb 15, 2021 - - ... - - Dribbble - - - $3.500 - - - Scheduled - - - View - -
- ... - - Darlene Robertson - - - Apr 15, 2021 - - ... - - Netguru - - - $2.750 - - - Postponed - - - View - -
- ... - - Theresa Webb - - - Mar 20, 2021 - - ... - - Figma - - - $4.200 - - - Scheduled - - - View - -
- ... - - Kristin Watson - - - Feb 15, 2021 - - ... - - Mailchimp - - - $3.500 - - - Not discussed - - - View - -
- ... - - Cody Fisher - - - Apr 10, 2021 - - ... - - Webpixels - - - $1.500 - - - Canceled - - - View - -
-
-
-
-
+ +
+
+
- {% block footer %} - {% include "partials/footer.html" %} - {% endblock footer %} -
- - - + + {% endblock content %} \ No newline at end of file diff --git a/base/templates/base/vendor.html b/base/templates/base/vendor.html index 41b49f9..dbd1c76 100644 --- a/base/templates/base/vendor.html +++ b/base/templates/base/vendor.html @@ -8,23 +8,7 @@
-
-
-
-
-
- -

Vendor Page

- -
- - -
- - -
-
-
+
@@ -49,10 +33,10 @@ - {{vendor.vendor}} + {{vendor.vendor}} - {{vendor.vendorWebName}} + {{vendor.vendorWebName|truncatechars:20}} @@ -74,8 +58,7 @@ {% endif %} - Edit - + Manage @@ -86,13 +69,6 @@
@@ -101,13 +77,5 @@ -
- {% block footer %} - {% include "partials/footer.html" %} - {% endblock footer %} -
- - - {% endblock content %} \ No newline at end of file diff --git a/base/templates/base/vendorEdit.html b/base/templates/base/vendorEdit.html index 1dc1218..9dd4ddb 100644 --- a/base/templates/base/vendorEdit.html +++ b/base/templates/base/vendorEdit.html @@ -3,103 +3,119 @@ {% block title %}Vendor Edit Page{% endblock title %} {% block content %} - + -
- -
-
-
-
-
- -

Edit page

+ + +
+
+
+
+
+

+ {% block vendor_title %} {{vendor_title}} {% endblock vendor_title %} +

+
+
+ +
- -
- - -
- - -
-
-
- -
-
- - - -
-
-
{% block vendor_title %} {{vendor_title}} {% endblock vendor_title %}
-
-
-
{% csrf_token %} {{ form.non_field_errors }} -
- -
-
-
- -
- + +
+
+
- Shown on invoice page - {% if form.vendorWebName.errors %} - {% for error in form.vendorWebName.errors %} - {{ error|escape }} +
+
+
+ + +
+ Looks good! +
+
+
+

+

+
+ +
+
+
+
+ + +
+ {% if form.vendorWebAddr.errors %} + {% for error in form.vendorWebAddr.errors %} + {{ error|escape }} + {% endfor %} + {% endif %} +
+
+
+

+

+
+ +
+
+ + {% if form.vendorNetworkFee.errors %} + {% for error in form.vendorNetworkFee.errors %} + {{ error|escape }} {% endfor %} {% endif %}
-
- -
-
-
- -
- +

+

+
+
- Shown on invoice page +
+
+ {% if form.vendorPaidNotification.errors %} + {% for error in form.vendorPaidNotification.errors %} + {{ error|escape }} + {% endfor %} + {% endif %}
-
- -
- - Skip the How-to screen on invoice page? +

+ +

+
+
-
-
- -
- - Get notifid by email for each payment received? -
-
-
- -
- @@ -112,19 +128,32 @@ - Amount covered by you in case the buyer sends less than required + {% if form.vendorCoverAmount.errors %} + {% for error in form.vendorCoverAmount.errors %} + {{ error|escape }} + {% endfor %} + {% endif %}
+

- -
- - When should we send you your payments? + {% if form.vendorPayWindow.errors %} + {% for error in form.vendorPayWindow.errors %} + {{ error|escape }} + {% endfor %} + {% endif %}
-
+
+
+
@@ -147,11 +176,7 @@
-
- {% block footer %} - {% include "partials/footer.html" %} - {% endblock footer %} -
+ diff --git a/base/urls.py b/base/urls.py index ba06898..8ce6f3b 100644 --- a/base/urls.py +++ b/base/urls.py @@ -5,9 +5,14 @@ from django.urls import path urlpatterns = [ path('login/', views.LoginPage, name="login"), path('logout/', views.LogoutPage, name="logout"), + path('', views.index, name="dashboard"), path('vendor/', views.VendorPage, name="vendor"), path('vendor/edit//', views.VendorEditPage, name="edit-vendor"), path('vendor/create', views.VendorCreatePage, name="create-vendor"), + path('vendor/address//', views.VendorAddrPage, name="add-address"), + + + #path('vendor-success', views.VendorSuccess, name="vendor-success") ] \ No newline at end of file diff --git a/base/views.py b/base/views.py index 8e8822d..a89c1b9 100644 --- a/base/views.py +++ b/base/views.py @@ -3,16 +3,19 @@ from django.contrib import messages from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ from django.db.models import Q +from django_otp.decorators import otp_required # from users.models import User -from .models import VendorsData -from .forms import VendorEditForm -from .functions import vendor_generator -import uuid +from .models import VendorsData, CryptoCoins, VendorsAddresses +from .forms import VendorEditForm, VendorAddrAddForm +from .functions import vendor_generator, checksumCheck +import uuid, hashlib # Create your views here. @login_required(login_url='login') +#@otp_required() def index(request): context = {} return render(request, 'base/dashboard.html', context) @@ -38,7 +41,7 @@ def LoginPage(request): context = {} - return render(request, 'base/login.html', context) + return render(request, 'base/loogin2.html', context) def LogoutPage(request): logout(request) @@ -51,41 +54,44 @@ def VendorPage(request): return render(request, 'base/vendor.html', context) @login_required(login_url='login') +#@otp_required(login_url='two_factor:login') def VendorEditPage(request, vdr ): - vendor = VendorsData.objects.get(vendorUUID=vdr) try: - if vendor.vendorid_id is not request.user.id: - messages.error(request, "You are not authorized to edit this vendor") - return redirect('vendor') - except Exception as e: - messages.error(request, "Something wrong happened, try again.") - return redirect('vendor') - - - form = VendorEditForm() - vendor_title = "Edit Vendor" - if request.method == 'POST': - if request.POST.get('submit') == 'Submit': - try: - vendors = VendorsData.objects.get(vendorUUID=vdr) - form = VendorEditForm(request.POST, instance=vendors) - if form.is_valid(): - form.save() - messages.success(request, "Vendor information saved") - return redirect('vendor') - else: + vendors = VendorsData.objects.get(Q(vendorid_id=request.user.id) & Q(vendorUUID=vdr)) + form = VendorEditForm() + vendor_title = "Edit Vendor" + if request.method == 'POST': + if request.POST.get('submit') == 'Submit': + try: + vendors = VendorsData.objects.get(Q(vendorid_id=request.user.id) & Q(vendorUUID=vdr)) + form = VendorEditForm(request.POST, instance=vendors) + if form.is_valid(): + form.save() + messages.success(request, "Vendor information saved") + return redirect('vendor') + else: + messages.error(request, "Something wrong happened, try again.") + except Exception as e: messages.error(request, "Something wrong happened, try again.") - except Exception as e: - messages.error(request, "Something wrong happened, try again.") - vendors = VendorsData.objects.get(vendorUUID=vdr) - context = {'vendors': vendors, 'form': form, 'vendor_title': vendor_title} - return render(request, 'base/vendorEdit.html', context) + context = {'vendors': vendors, 'form': form, 'vendor_title': vendor_title} + return render(request, 'base/vendorEdit.html', context) + except Exception as e: + messages.error(request, e) + return redirect('vendor') @login_required(login_url='login') def VendorCreatePage(request): vendor_title = "Create Vendor" + context = {'vendor_title': vendor_title} if request.method == 'POST': try: + try: + vendor_accounts = len(VendorsData.objects.filter(vendorid_id=request.user.id)) + if vendor_accounts > request.user.vendornr: + messages.error(request, "You have reached the maximum number of vendor accounts") + return redirect('vendor') + except: pass + form = VendorEditForm(request.POST) if form.is_valid(): # Generate vendor @@ -99,22 +105,80 @@ def VendorCreatePage(request): except: pass + secretKey = str(uuid.uuid4()) + md5s = hashlib.md5(secretKey.encode()).hexdigest() new_vendor.vendor = vdr - new_vendor.vendorSecretKey = uuid.uuid4() + new_vendor.vendorSecretKey = md5s + new_vendor.vendorSkipScreen = False new_vendor.vendorUUID = uuid.uuid4() new_vendor.vendorid_id = request.user.id # Save vendor try: new_vendor.save() - messages.success(request, "Vendor created") - return redirect('vendor') - except Exception as e: - print(e) + vendor_title = "Congrats!" + context = {'vendor_title': vendor_title, "vendor": new_vendor, 'secret': secretKey} + return render(request, 'base/vendorSuccess.html', context) + except Exception: + messages.error(request, "Unable to create vendor") # else: - messages.error(request, "Something wrong happened, try again.") + messages.error(request, "The information submited is incomplete") # except Exception as e: messages.error(request, "Something wrong happened, try again.") + return render(request, 'base/vendorCreate.html', context) + +@login_required(login_url='login') +def VendorAddrPage(request,vdr): + try: + coins = CryptoCoins.objects.filter(coinIsActive=True) + try: + vendors = VendorsData.objects.get(Q(vendorid_id=request.user.id) & Q(vendorUUID=vdr)) + except: + messages.error(request, "Vendor not found") + return redirect('vendor') + addresses = VendorsAddresses.objects.filter(Q(vendorUUID=vdr) & Q(vendorid_id=request.user.id)) + form = VendorAddrAddForm() + vendor_title = "Add a crypto address" + if request.method == 'POST': + if request.POST.get('submit') == 'Submit': + try: + if checksumCheck(request.POST.get('coin').lower(), request.POST.get('address')) == False: + messages.error(request, "Invalid address") + context = {'vendor_title': vendor_title, 'coins': coins, 'vendors': vendors, "addresses": addresses} + return render(request, 'base/vendorAddr.html', context) + except: + # stop here, return nothing, notify us. + return redirect('vendorAddr', vdr) + ##check if duplicate - context = {'vendor_title': vendor_title} - return render(request, 'base/vendorEdit.html', context) \ No newline at end of file + try: + form = VendorAddrAddForm(request.POST) + if form.is_valid(): + new_address = form.save(commit=False) + new_address.vendorUUID = vdr + new_address.addrDeleted = False + new_address.addrIsActive = True + new_address.vendor_id = vendors.id + new_address.vendorid_id = request.user.id + try: + for obj in VendorsAddresses.objects.filter(Q(coin=request.POST.get('coin')) & Q(addrIsActive=True) & Q(vendorid_id=request.user.id)): + obj.addrIsActive = False + obj.save() + new_address.save() + messages.success(request, "Vendor address saved") + #add email notification + context = {'vendor_title': vendor_title, 'coins': coins, 'vendors': vendors, "addresses": addresses} + return render(request, 'base/vendorAddr.html', context) + except Exception as e: + messages.error(request, e) # + else: + messages.error(request, "Something wrong happened, try again!") + except Exception as e: + messages.error(request, "Something wrong happened, try again!") + + context = {'vendor_title': vendor_title, 'coins': coins, 'vendors': vendors, "addresses": addresses} + return render(request, 'base/vendorAddr.html', context) + except Exception as e: + messages.error(request, "Something wrong happened, try again!") + return redirect('vendor') + diff --git a/src/.DS_Store b/src/.DS_Store index fb7f9d8f8ba041f8c4e232eaa8cea219f16dcd59..965bed2c64d4f71cb1cb746b1f2534665dd4d91f 100644 GIT binary patch delta 455 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{MGjUEV6q~50D9Q$s2ZA@+S0;6N5Rb8qPCWkLsVJcIw(FnCpRy@3+P-RU<7&w z40xe5jOqq5px!A9F3QWv&r1i2fK)RxWHRJ3r01j?1}Ep|7C<$4f@r8zZoZ2PG;}!5 z1YZz%%z4xiSvCcaY(d7xxUDRU**Q1_nIT3Ca06*qP#kP5{LVa?U&av>Ng$`NfM_O2 NEP=%~$Mei#1^^jjW}E;3 literal 8196 zcmeHMF>4e-6n^8a@xUgC;&m=TQss)+DA=swgxJ_zVWCC5M9;|GZSaiscK!h?3maQg ziA6e#fLK`DXd?(>r3P#i@q2GxE;GA(SO{|9n`7rY=6i3xnQw1*d3!|UR<<{miCRR| z#KAa!99>Ogo{vg9q9bR44*5j)X*}pJch~dbRwdd7%mH)2954sW0drs$9KbW1TXV{D z-?P@61LnYg=>Xp!QXGt#V&PG49q6P4045lghVQ5YBqvqO6bp}%iaCAt;55}~iQ#-Y z_>{wenPTD5r<3#PCFKO3AMEb8&)}NzI8p8WNoR4??_QQiR zUs}V%rBmvAFh^+>wK35;_IVk&6W=ZAFYKK@7>{*}kH5}~uH?jeF&F79uCY$SI_!9K zhH3Xo$i;f}_}LlJo8-otRX)HUFAx;Z9T^cP59{7RpF=TZc;Dy zw(;is3)K}a)ctYd>F2B8biV`qI*=Rgw~h~WleX@6TyrJOf87;b}-2{;31M E0cZpZEC2ui diff --git a/templates/main.html b/templates/main.html index 4323902..05d96ea 100644 --- a/templates/main.html +++ b/templates/main.html @@ -18,9 +18,7 @@
- {% block footer %} - {% include "partials/footer.html" %} - {% endblock footer %} +
{% endblock content %} \ No newline at end of file diff --git a/templates/partials/base.html b/templates/partials/base.html index c2afdba..1e9d852 100755 --- a/templates/partials/base.html +++ b/templates/partials/base.html @@ -1,25 +1,27 @@ {% load static %} - - + + + - - {% block title %}{% endblock title %} | - - - - - - {% block css %} - - - - - - - {% endblock css %} + + + + {% block title %}{% endblock title %} | + + + {% block css %} + + + + + + + + {% endblock css %} - + +
{% block header %} @@ -31,8 +33,6 @@ {% block content %} {% block pagetitle %} {% endblock pagetitle %} - {% block footer %} - {% endblock footer %} {% endblock content %}
diff --git a/templates/partials/footer.html b/templates/partials/footer.html index e1e6407..73f30da 100755 --- a/templates/partials/footer.html +++ b/templates/partials/footer.html @@ -1,10 +1,9 @@ {% block footer %} -