re-added labellabor

This commit is contained in:
Xavier Fiechter 2024-01-07 08:14:34 +01:00
parent a4f9389d28
commit a7c9442aee
8 changed files with 1246 additions and 0 deletions

View file

16
django/labellabor/asgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
ASGI config for labellabor 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.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "labellabor.settings")
application = get_asgi_application()

View file

@ -0,0 +1,48 @@
#!/usr/bin/env python3
import os
import string
import secrets
import configparser
import time
def generate_random_string(length):
# Exclude curly braces '{}' from the pool of characters
characters = string.ascii_letters + string.digits
return ''.join(secrets.choice(characters) for _ in range(length))
def generate_config_file(config_file_path="config.ini"):
config = configparser.ConfigParser()
# Generate random values for password and secret_key with specified lengths
dj_secret_key = generate_random_string(50)
# Set the values in the configuration file
config['internal'] = {
'secret_key': '{}'.format(dj_secret_key),
'proj_name': 'labelbase',
'crypto_salt': 'labelbase_',
'allowed_host': '*',
'debug': True,
'current_timestamp_seconds': int(time.time())
}
config['database'] = {
'name': 'labelbase',
'user': 'ulabelbase',
'password': 'vrZvZmX6Kp16B9tTa8JAA4RtAkWEhi',
# 'host': '127.0.0.1'
}
# Create the configuration file and write the values
with open(config_file_path, 'w') as configfile:
config.write(configfile)
if __name__ == "__main__":
# Check if the config.ini file exists
if not os.path.isfile(config_file_path):
generate_config_file()
else:
print("{} already exists.".format(config_file_path))

View file

@ -0,0 +1,229 @@
import os
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
from pathlib import Path
from configparser import RawConfigParser
# Build paths inside the project like this: BASE_DIR / 'subdir'.
PROJECT_PATH = BASE_DIR = Path(__file__).resolve().parent.parent
proj_config = RawConfigParser()
config_file_path = "{}/config.ini".format(PROJECT_PATH)
try:
if proj_config.read(config_file_path) == []:
from .make_config import generate_config_file
if not os.path.isfile(config_file_path):
print("creating config file, here: {}".format(config_file_path))
generate_config_file(config_file_path)
assert proj_config.read(config_file_path) == []
except AssertionError:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured(
"Configuration file {} not found!".format(config_file_path)
)
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = proj_config.get("internal", "secret_key")
CRYPTOGRAPHY_SALT = proj_config.get("internal", "crypto_salt")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = proj_config.getboolean("internal", "debug")
if DEBUG:
ALLOWED_HOSTS = ["*"]
else:
ALLOWED_HOSTS = [proj_config.get("internal", "allowed_host")]
sentry_sdk.init(
dsn="https://3b833ae08ccc4ff68793e961fff4921c@o4504646963232768.ingest.sentry.io/4504646967361536",
integrations=[
DjangoIntegration(),
],
# Set traces_sample_rate to 1.0 to capture 100%
# of transactions for performance monitoring.
# We recommend adjusting this value in production.
traces_sample_rate=1.0,
# If you wish to associate users to errors (assuming you are using
# django.contrib.auth) you may enable sending PII data.
send_default_pii=False,
)
sentry_sdk.set_tag("version", "0.23.1")
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler',
'filename': './labelbase.log',
'maxBytes': 1024 * 1024 * 5, # 5 MB
'backupCount': 5, # Keep 5 backup files
'formatter': 'verbose',
},
},
'loggers': {
'labelbase': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
# 'django.contrib.sessions', # NOTE: We use "user_sessions"
"user_sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"two_factor",
"two_factor.plugins.phonenumber",
"django_otp",
"django_otp.plugins.otp_static",
"django_otp.plugins.otp_totp",
"labelbase",
"userprofile",
"bootstrapform",
"cryptography",
"rest_framework",
"rest_framework.authtoken",
"sekizai",
"importer",
"finances",
"background_task",
"connectrum",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
#"django.middleware.cache.UpdateCacheMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django_otp.middleware.OTPMiddleware",
#"django.middleware.cache.FetchFromCacheMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
#CACHES = {
# 'default': {
# 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
# 'LOCATION': 'labelbase-snowflake',
# }
#}
ROOT_URLCONF = "labellabor.urls"
LOGIN_URL = "two_factor:login"
LOGIN_REDIRECT_URL = "two_factor:profile"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(PROJECT_PATH, "templates")],
"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",
"sekizai.context_processors.sekizai",
],
},
},
]
CURRENCIES = ['USD', 'EUR', 'GBP', 'CAD', 'CHF', 'AUD', 'JPY']
WSGI_APPLICATION = "labellabor.wsgi.application"
TWO_FACTOR_REMEMBER_COOKIE_AGE = 60 * 60 * 24 * 14
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": proj_config.get("database", "name"),
"USER": proj_config.get("database", "user"),
"OPTIONS": {"charset": "utf8mb4"},
"PASSWORD": proj_config.get("database", "password"),
'HOST': 'localhost',
'PORT': 3306,
}
}
TWO_FACTOR_WEBAUTHN_RP_NAME = "labelbase.space"
SESSION_ENGINE = "user_sessions.backends.db"
REST_FRAMEWORK = {"DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema"}
# Password validation
# https://docs.djangoproject.com/en/3.2/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.2/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.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-FILE_UPLOAD_HANDLERS
FILE_UPLOAD_HANDLERS = [
"django.core.files.uploadhandler.MemoryFileUploadHandler",
"django.core.files.uploadhandler.TemporaryFileUploadHandler",
]

250
django/labellabor/urls.py Normal file
View file

@ -0,0 +1,250 @@
from django.contrib import admin
from django.contrib.auth.views import LogoutView
from django.urls import include, path
from two_factor.urls import urlpatterns as tf_urls
from labelbase.api import LabelAPIView, LabelbaseAPIView
from rest_framework.documentation import include_docs_urls
from django.contrib.auth.decorators import login_required
from userprofile.views import (ProfileView,
ProfileAvatarUpdateView,
ProfileCurrencyUpdateView)
from userprofile.views import APIKeyView
from .views import (
LabelbaseView,
LabelbaseViewActionView,
LabelbaseMergeView,
LabelbaseDeleteView,
LabelDeleteView,
TermsView,
HomeView,
RegistrationCompleteView,
LabelUpdateView,
PrivacyView,
RegistrationView,
LabelbaseFormView,
LabelbaseUpdateView,
FaqView,
AboutView,
EncryptionView,
InteroperationalView,
ExportLabelsView,
#StatsAndKPIView,
TreeMapsView,
FixAndMergeLabelsView,
LabelbaseDatatableView,
LabelbasePortfolioView,
)
from importer.views import upload_labels
from exporter.views import stream_labels_as_jsonl
from django.contrib.auth import views as auth_views
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path(
"api/v0/labelbase/",
LabelbaseAPIView.as_view()
),
path(
"api/v0/labelbase/<int:id>/",
LabelbaseAPIView.as_view()
),
path(
"api/v0/labelbase/<int:labelbase_id>/label/",
LabelAPIView.as_view()
),
path(
"api/v0/labelbase/<int:labelbase_id>/label/<int:id>/",
LabelAPIView.as_view()
),
path(
"api-reference/",
include_docs_urls(title="Labelbase API")
),
path(
"account/apikey/",
login_required(APIKeyView.as_view()),
name="apikey"
),
path(
"account/userprofile-avatar/",
login_required(ProfileAvatarUpdateView.as_view()),
name="userprofile_avatar",
),
path(
"account/userprofile-currency/",
login_required(ProfileCurrencyUpdateView.as_view()),
name="userprofile_currency",
),
path(
"account/userprofile/",
login_required(ProfileView.as_view()),
name="userprofile",
),
path(
"labelbase/<pk>/delete/",
login_required(LabelbaseDeleteView.as_view()),
name="del_labelbase"
),
path(
"labelbase/<int:labelbase_id>/",
login_required(LabelbaseView.as_view()),
name="labelbase"
),
path(
"labelbase/<int:labelbase_id>/data/",
login_required(LabelbaseDatatableView.as_view()),
name="labelbase_label_data"),
path(
"labelbase/<int:labelbase_id>/actions/<str:action>/",
login_required(LabelbaseViewActionView.as_view()),
name="labelbase_actions"),
path(
"labelbase/<int:labelbase_id>/merge/",
login_required(LabelbaseMergeView.as_view()),
name="labelbase_merge"
),
path(
"labelbase/<int:labelbase_id>/portfolio/",
login_required(LabelbasePortfolioView.as_view()),
name="labelbase_portfolio"
),
path(
"labelbase/<int:labelbase_id>/edit/",
login_required(LabelbaseUpdateView.as_view()),
name="edit_labelbase"
),
path(
"labelbase/import/",
upload_labels,
name="import_labels"
),
path(
"labelbase/export/<int:labelbase_id>/",
stream_labels_as_jsonl,
name="export_labels"
),
path(
"labelbase/<int:labelbase_id>/dyanmic-export/",
login_required(ExportLabelsView.as_view()),
name="labelbase_dynamic_export"
),
path(
"labelbase/<int:labelbase_id>/fix-and-manage/",
login_required(FixAndMergeLabelsView.as_view()),
name="labelbase_fix_and_manage"
),
#path(
# "labelbase/<int:labelbase_id>/stats-and-kpi/",
# login_required(StatsAndKPIView.as_view()),
# name="labelbase_stats_and_kpi"
#),
path(
"labelbase/<int:pk>/tree-maps/",
login_required(TreeMapsView.as_view()),
name="labelbase_tree_maps"
),
path(
"labelbase/<int:pk>/tree-maps/<str:action>/",
login_required(TreeMapsView.as_view()),
name="labelbase_tree_maps"
),
path(
"labelbase/",
login_required(LabelbaseFormView.as_view()),
name="labelbase_new"
),
path(
"label/<int:pk>/edit/",
login_required(LabelUpdateView.as_view()),
name="edit_label"
),
path(
"label/<int:pk>/edit/<str:action>/",
login_required(LabelUpdateView.as_view()),
name="edit_label_with_action"
),
path(
"label/<int:pk>/delete/",
login_required(LabelDeleteView.as_view()),
name="del_label"
),
path(
"account/logout/",
LogoutView.as_view(),
name="logout"
),
path(
"account/register/",
RegistrationView.as_view(),
name="registration"
),
path(
"account/register/done/",
RegistrationCompleteView.as_view(),
name="registration_complete"
),
path(
"account/change-password/",
auth_views.PasswordChangeView.as_view(
template_name="change_password.html",
success_url="/"
),
name="change_password"
),
path(
"privacy-policy",
PrivacyView.as_view(),
name="privacy_policy"
),
path(
"terms",
TermsView.as_view(),
name="terms"
),
path(
"faq",
FaqView.as_view(),
name="faq"
),
path(
"about",
AboutView.as_view(),
name="about"
),
path(
"encryption",
EncryptionView.as_view(),
name="encryption"
),
path(
"interoperational",
InteroperationalView.as_view(),
name="interoperational"
),
path(
"",
HomeView.as_view(),
name="home"
),
path(
"",
include(tf_urls)
),
path(
"",
include("user_sessions.urls", "user_sessions")
),
]
#urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
#if settings.DEBUG:
urlpatterns.append(path("admin/", admin.site.urls))

View file

@ -0,0 +1,37 @@
import re
import decimal
from django.conf import settings
def hashtag_to_badge(value):
hashtags = re.findall(r'#\w+', value)
for tag in hashtags:
clean_tag = re.search(r'#(\w+)', tag).group(1) # Extract the word after the '#' symbol
value = value.replace(tag, f'<a href="?tag={clean_tag}" class="badge badge-hashtag">{clean_tag}</a>')
return value
def extract_fiat_value(s):
# CHF XXX
pattern = r"({})\s?(\d+(?:\.\d+)?)"
for currency in settings.CURRENCIES:
match = re.search(pattern.format(currency), s)
if match:
print(f"Currency: {match.group(1)}, Value: {match.group(2)}")
return (decimal.Decimal(match.group(2)), match.group(1))
# XXX CHF
pattern = r"(\d+(?:\.\d+)?)\s?({})"
for currency in settings.CURRENCIES:
match = re.search(pattern.format(currency), s)
if match:
value = decimal.Decimal(match.group(1))
currency_symbol = currency
# Find the currency symbol position in the string
symbol_start = match.start(2)
symbol_end = match.end(2)
if symbol_start > 0:
currency_symbol = s[symbol_start:symbol_end]
print(f"Currency: {currency_symbol}, Value: {value}")
return (value, currency_symbol)
return (decimal.Decimal(-1), "")

650
django/labellabor/views.py Normal file
View file

@ -0,0 +1,650 @@
import os
import time
import tempfile
import json
from django.conf import settings
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import redirect, resolve_url
from django.views.decorators.cache import never_cache
from django.views.generic import FormView, TemplateView
from django.views import View
from django.shortcuts import render
from django.views.generic.list import ListView
from django.views.generic.edit import DeleteView
from django.views.generic.edit import UpdateView
from django.shortcuts import get_object_or_404
from two_factor.views import OTPRequiredMixin
from two_factor.views.utils import class_view_decorator
from labelbase.models import Label, Labelbase
from labelbase.forms import LabelForm, LabelbaseForm
from labelbase.forms import ExportLabelsForm
from django.http import HttpResponseRedirect
from django.http import FileResponse
from rest_framework.authtoken.models import Token
from bip329.bip329_writer import BIP329JSONLWriter, BIP329JSONLEncryptedWriter
from django.utils.safestring import mark_safe
from django.urls import reverse
from django_datatables_view.base_datatable_view import BaseDatatableView
from django.utils.html import escape
from django.template.loader import render_to_string
from .utils import hashtag_to_badge, extract_fiat_value
from labelbase.utils import compute_type_ref_hash
from finances.models import OutputStat
from finances.tasks import check_all_outputs
class AboutView(TemplateView):
template_name = "about.html"
class EncryptionView(TemplateView):
template_name = "encryption.html"
class InteroperationalView(TemplateView):
template_name = "interoperational.html"
class HomeView(TemplateView):
template_name = "home.html"
class PrivacyView(TemplateView):
template_name = "privacy.html"
class TermsView(TemplateView):
template_name = "terms.html"
class FaqView(TemplateView):
template_name = "faq.html"
class LabelDeleteView(DeleteView):
model = Label
success_url = "/"
error_url = "/#failed"
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.labelbase.user != self.request.user:
return redirect(self.error_url)
return super().post(request, *args, **kwargs)
class LabelbaseDeleteView(DeleteView):
model = Labelbase
success_url = "/"
error_url = "/#failed"
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.user != self.request.user:
return redirect(self.error_url)
return super().post(request, *args, **kwargs)
class LabelbaseDatatableView(BaseDatatableView):
model = Label
columns = ["id", "type", "ref", "label", "origin", "spendable"]
order_columns = ["id", "type", "ref", "label", "origin", "spendable"]
max_display_length = 100
def get_initial_queryset(self):
labelbase_id = self.kwargs["labelbase_id"]
search_tag = self.request.GET.get('tag', None)
qs = Label.objects.filter(labelbase__user_id=self.request.user.id,
labelbase_id=labelbase_id).order_by("id")
if search_tag:
# Due to encryption, we need to use a super slow process here...
res_ids = []
search = f'#{search_tag}'
print(search)
for record in qs:
if record.label and search in record.label:
res_ids.append(record.id)
continue
qs = qs.filter(id__in=res_ids)
return qs
def render_column(self, row, column):
if column == 'id':
return f'<tt><a href="{reverse("edit_label", args=[row.id])}">{row.id}</a></tt>'
elif column == 'type':
return "<tt>{}</tt>".format(row.get_type_display())
elif column == 'ref':
return render_to_string('labelbase_dt_ref.html',
context={'row': row,
'mempool_url': row.get_mempool_url(),
}, request=self.request)
elif column == 'label':
if row.label is None:
return ""
if row.labelbase.user.profile.use_hashtags:
return hashtag_to_badge(f'<tt>{row.label}</tt>')
else:
return f'<tt>{row.label}</tt>'
elif column == 'origin':
if row.origin is None:
return ""
return f'<tt>{row.origin}</tt>'
elif column == 'spendable':
spendable_value = row.spendable
if spendable_value is None:
spendable_formatted = ''
else:
spendable_formatted = 'true' if spendable_value else 'false'
#if spendable_formatted == 'true':
# return f'<span class="badge badge-spendable fs--2 "><svg xmlns="http://www.w3.org/2000/svg" width="16px" height="16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check ms-1" style="height:12.8px;width:12.8px;"><polyline points="20 6 9 17 4 12"></polyline></svg> <span class="badge-label"><tt>{spendable_formatted }</tt></span> </span>'
#elif spendable_formatted == 'false':
# return f'<span class="badge badge-unspendable fs--2 "><svg xmlns="http://www.w3.org/2000/svg" width="12.4" height="12.4" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 6L6 18M6 6l12 12"/></svg><span class="badge-label"><tt>{spendable_formatted }</tt></span></span>'
return f'<tt>{spendable_formatted }</tt>'
else:
return super(LabelbaseDatatableView, self).render_column(row, column)
def filter_queryset(self, qs):
search = self.request.GET.get('search[value]', None)
if search:
# Due to encryption, we need to use a super slow process here...
res_ids = []
search = search.lower()
for record in qs:
if record.type and search in record.type.lower():
res_ids.append(record.id)
continue
if record.ref and search in record.ref.lower():
res_ids.append(record.id)
continue
if record.label and search in record.label.lower():
res_ids.append(record.id)
continue
if record.origin and search in record.origin.lower():
res_ids.append(record.id)
continue
return qs.filter(id__in=res_ids)
return qs
class LabelbaseViewActionView(View):
def get(self, request, *args, **kwargs):
labelbase = get_object_or_404(
Labelbase, id=self.kwargs["labelbase_id"], user_id=self.request.user.id
)
if self.kwargs["action"] == "update-spent-outputs":
check_all_outputs(self.request.user.id, labelbase_id=labelbase.id)
return HttpResponseRedirect(labelbase.get_absolute_url())
class LabelbaseView(ListView):
template_name = "labelbase.html"
context_object_name = "label_list"
def get_queryset(self):
qs = Label.objects.filter(
labelbase__user_id=self.request.user.id,
labelbase_id=self.kwargs["labelbase_id"],
)
return qs.order_by("id")
def get_context_data(self, **kwargs):
labelbase_id = self.kwargs["labelbase_id"]
context = super(LabelbaseView, self).get_context_data(**kwargs)
context["labelbase"] = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
context["active_labelbase_id"] = labelbase_id
context["labelform"] = LabelForm(
request=self.request, labelbase_id=labelbase_id
)
context["api_token"] = Token.objects.get(user_id=self.request.user.id)
return context
def post(self, request, *args, **kwargs):
labelbase_id = self.kwargs["labelbase_id"]
labelform = LabelForm(request.POST, request=request, labelbase_id=labelbase_id)
if labelform.is_valid():
label = labelform.save()
return HttpResponseRedirect(label.labelbase.get_absolute_url())
class LabelbaseMergeView(LabelbaseView):
template_name = "labelbase_merge.html"
def get_queryset(self):
labels = []
qs = Label.objects.filter(
labelbase__user_id=self.request.user.id,
labelbase_id=self.kwargs["labelbase_id"],
)
lbl_type = self.request.GET.get("type", None)
lbl_ref = self.request.GET.get("ref", None)
lbl_label = self.request.GET.get("label", None)
for l in qs:
if lbl_type and lbl_ref and lbl_label:
if l.type == lbl_type and l.ref == lbl_ref and l.label == lbl_label:
labels.append(l)
elif lbl_type and lbl_ref:
if l.type == lbl_type and l.ref == lbl_ref:
labels.append(l)
elif lbl_type and lbl_label:
if l.type == lbl_type and l.label == lbl_label:
labels.append(l)
elif lbl_ref and lbl_label:
if l.ref == lbl_ref and l.label == lbl_label:
labels.append(l)
elif lbl_type:
if l.type == lbl_type:
labels.append(l)
elif lbl_ref:
if l.ref == lbl_ref:
labels.append(l)
elif lbl_label or lbl_label == '':
if l.label == lbl_label:
labels.append(l)
label_ids = []
for l in labels:
label_ids.append(l.id)
if label_ids:
qs = Label.objects.filter(id__in=label_ids,
labelbase__user_id=self.request.user.id,
labelbase_id=self.kwargs["labelbase_id"])
else:
qs = Label.objects.none()
return qs.order_by("id")
class StatsAndKPIView(View):
template_name = "labelbase_stats_and_kpi.html"
def get(self, request, *args, **kwargs):
labelbase_id = self.kwargs["labelbase_id"]
labelbase = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
return render(request, self.template_name, {
"labelbase": labelbase,
"active_labelbase_id": labelbase_id})
"""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["active_labelbase_id"] = self.object.labelbase.id
context["labelbase"] = self.object.labelbase
context["action"] = self.kwargs.get('action', 'unspent-outputs')
if context["action"] == "labeling":
#mempool_api = self.object.labelbase.get_mempool_api()
if self.object.type == "tx":
mempool_api = self.object.labelbase.get_mempool_api()
context["res_tx"] = mempool_api.get_transaction(self.object.ref)
if self.object.type == "output":
context["output"] = OutputStat.objects.filter(type_ref_hash= \
self.object.type_ref_hash).last()
return context
"""
class TreeMapsView(ListView):
context_object_name = "label_list"
def get_template_names(self):
if 'action' in self.kwargs:
action = self.kwargs['action']
if action == 'unspent-outputs':
return "labelbase_tree_maps_unspent_outputs.html"
elif action == 'unspent-spendable-outputs':
return "labelbase_tree_maps_unspent_outputs.html"
return "labelbase_tree_maps_unspent_outputs.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
labelbase_id = self.kwargs["pk"]
labelbase = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
context['labelbase'] = labelbase
context['active_labelbase_id'] = labelbase.id
context['action'] = self.kwargs.get('action', 'unspent-outputs')
return context
def get_queryset(self):
#action = self.kwargs.get('action', 'unspent-outputs')
label_ids = []
qs = Label.objects.filter(
labelbase__user_id=self.request.user.id,
labelbase_id=self.kwargs["pk"],
)
for l in qs:
if l.type == "output":
output = OutputStat.objects.filter(
type_ref_hash=l.type_ref_hash,
network=l.labelbase.network).last()
if output and output.spent is False:
#if action == 'unspent-spendable-outputs' and l.spendable == True:
# label_ids.append(l.id)
#else:
label_ids.append(l.id)
if label_ids:
qs = qs.filter(id__in=label_ids,
labelbase__user_id=self.request.user.id,
labelbase_id=self.kwargs["pk"])
else:
qs = Label.objects.none()
return qs.order_by("id")
class LabelbasePortfolioView(LabelbaseView):
template_name = "labelbase_portfolio.html"
def get_queryset(self):
self.balances = {}
label_ids = []
qs = Label.objects.filter(
labelbase__user_id=self.request.user.id,
labelbase_id=self.kwargs["labelbase_id"],
)
for l in qs:
if l.type == "output":
val, cur = extract_fiat_value(l.label)
if val > 0:
if self.balances.get(cur, None) is None:
self.balances[cur] = 0
self.balances[cur] += val
label_ids.append(l.id)
if label_ids:
qs = Label.objects.filter(id__in=label_ids,
labelbase__user_id=self.request.user.id,
labelbase_id=self.kwargs["labelbase_id"])
else:
qs = Label.objects.none()
return qs.order_by("id")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['balances'] = self.balances
return context
class FixAndMergeLabelsView(View):
template_name = "label_fix_and_merge.html"
def get(self, request, *args, **kwargs):
labelbase_id = self.kwargs["labelbase_id"]
labelbase = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
# Fetch all records for the specified Labelbase
records = Label.objects.filter(labelbase_id=labelbase_id)
# Create a dictionary to store records grouped by type, ref, and label
record_groups_type_and_ref = {}
record_groups_type_and_ref_and_label = {}
record_groups_all_identical = {}
resulting_duplicates_type_and_ref = []
resulting_duplicates_type_and_ref_and_label = []
resulting_duplicates_all_identical = []
all_identical_records = []
# Iterate through the records and group them
for record in records:
key_type_and_ref = (record.type, record.ref)
key_type_and_ref_label = (record.type, record.ref, record.label)
key_all_identical = (record.type, record.ref, record.label, record.origin, record.spendable)
if key_type_and_ref in record_groups_type_and_ref:
record_groups_type_and_ref[key_type_and_ref].append(record)
else:
record_groups_type_and_ref[key_type_and_ref] = [record]
if key_type_and_ref_label in record_groups_type_and_ref_and_label:
record_groups_type_and_ref_and_label[key_type_and_ref_label].append(record)
else:
record_groups_type_and_ref_and_label[key_type_and_ref_label] = [record]
if key_all_identical in record_groups_all_identical:
record_groups_all_identical[key_all_identical].append(record)
else:
record_groups_all_identical[key_all_identical] = [record]
# Iterate through the grouped records and identify duplicates
for key_type_and_ref, duplicates in record_groups_type_and_ref.items():
if len(duplicates) > 1:
type_val, ref_val = key_type_and_ref
print(f"Duplicates for labelbase_id={labelbase_id}, type='{type_val}', ref='{ref_val}', ")
for record in duplicates:
print(f" ID: {record.id}, origin: {record.origin}, spendable: {record.spendable}")
resulting_duplicates_type_and_ref.append({'type': type_val, 'ref': ref_val})
for key_type_and_ref_and_label, duplicates in record_groups_type_and_ref_and_label.items():
if len(duplicates) > 1:
type_val, ref_val, label_val = key_type_and_ref_and_label
print(f"Duplicates for labelbase_id={labelbase_id}, type='{type_val}', ref='{ref_val}', label='{label_val}':")
for record in duplicates:
print(f" ID: {record.id}, origin: {record.origin}, spendable: {record.spendable}")
resulting_duplicates_type_and_ref_and_label.append({'type': type_val, 'ref': ref_val, 'label': label_val})
for key_all_identical, duplicates in record_groups_all_identical.items():
if len(duplicates) > 1:
type_val, ref_val, label_val, origin_val, spendable_cal = key_all_identical
#print(f"Duplicates for labelbase_id={labelbase_id}, type='{type_val}', ref='{ref_val}', label='{label_val}':")
for record in duplicates:
if record not in all_identical_records:
all_identical_records.append(record)
# print(f" ID: {record.id}, origin: {record.origin}, spendable: {record.spendable}")
resulting_duplicates_all_identical.append({'type': type_val, 'ref': ref_val, 'label': label_val,
'origin': origin_val, 'spendable': spendable_cal}) # nonsense, but counts
fix_suggestions = len(resulting_duplicates_type_and_ref) + \
len(resulting_duplicates_type_and_ref_and_label) + \
len(resulting_duplicates_all_identical)
return render(request, self.template_name, {
"labelbase": labelbase,
"fix_suggestions": fix_suggestions,
"resulting_duplicates_all_identical_final_record_count": len(resulting_duplicates_all_identical),
"resulting_duplicates_all_identical_current_record_count": len(all_identical_records),
"resulting_duplicates_type_and_ref": resulting_duplicates_type_and_ref,
"resulting_duplicates_type_and_ref_and_label": resulting_duplicates_type_and_ref_and_label,
"active_labelbase_id": labelbase_id})
class ExportLabelsView(View):
def post(self, request, *args, **kwargs):
labelbase_id = self.kwargs["labelbase_id"]
labelbase = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
form = ExportLabelsForm(request.POST)
if form.is_valid():
# Retrieve data from the form
tx_checkbox = form.cleaned_data['tx_checkbox']
addr_checkbox = form.cleaned_data['addr_checkbox']
pubkey_checkbox = form.cleaned_data['pubkey_checkbox']
input_checkbox = form.cleaned_data['input_checkbox']
output_checkbox = form.cleaned_data['output_checkbox']
xpub_checkbox = form.cleaned_data['xpub_checkbox']
encrypt_checkbox = form.cleaned_data['encrypt_checkbox']
# Use request.POST, not form.cleaned_data['passphrase']
passphrase = request.POST.get('passphrase', '')
# Export labels based on selected checkboxes
selected_type_attributes = []
if tx_checkbox:
selected_type_attributes.append('tx')
if addr_checkbox:
selected_type_attributes.append('addr')
if pubkey_checkbox:
selected_type_attributes.append('pubkey')
if input_checkbox:
selected_type_attributes.append('input')
if output_checkbox:
selected_type_attributes.append('output')
if xpub_checkbox:
selected_type_attributes.append('xpub')
timestamp = time.strftime('%Y%m%d%H%M%S')
# Define the prefix and file extension for the temporary file
prefix = f'labelbase-{labelbase.id}-{timestamp}-'
suffix = '.jsonl'
# Create a temporary file with the specified prefix and file extension
with tempfile.NamedTemporaryFile(delete=False, prefix=prefix, suffix=suffix) as temp_file:
# Create a BIP329JSONLWriter instance
if encrypt_checkbox and passphrase:
label_writer = BIP329JSONLEncryptedWriter(temp_file.name, passphrase, remove_existing=True)
else:
label_writer = BIP329JSONLWriter(temp_file.name, remove_existing=True)
labels = Label.objects.filter(labelbase_id=labelbase_id,
labelbase__user_id=request.user.id,
type__in=selected_type_attributes
).order_by("id")
for label in labels:
label_entry = {
"type": label.type,
"ref": label.ref,
"label": label.label,
}
if label.origin and label.type == "tx":
label_entry["origin"] = label.origin
if label.spendable in [True, False] and label.type == "output":
label_entry["spendable"] = label.spendable
# Write the label entry to the file
label_writer.write_label(label_entry)
final_filename = f"labelbase-{labelbase.id}-{timestamp}.jsonl"
if encrypt_checkbox and passphrase:
if label_writer.is_closed is False:
# Close the encrypted writer when finished
label_writer.close()
final_filename = final_filename.replace(".jsonl", ".7z")
response = FileResponse(open(temp_file.name, 'rb'))
response['Content-Type'] = 'application/json'
response['Content-Disposition'] = f'attachment; filename="{final_filename}"'
os.remove(temp_file.name)
return response
# TODO: error message
return HttpResponseRedirect(labelbase.get_absolute_url())
class LabelUpdateView(UpdateView):
model = Label
fields = ["type", "ref", "label", "origin", "spendable"]
def get_template_names(self):
if 'action' in self.kwargs:
action = self.kwargs['action']
if action == 'labeling':
return "label_edit_labeling.html"
return "label_edit_update.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["active_labelbase_id"] = self.object.labelbase.id
context["labelbase"] = self.object.labelbase
context["action"] = self.kwargs.get('action', 'update')
if context["action"] == "labeling":
#mempool_api = self.object.labelbase.get_mempool_api()
if self.object.type == "tx":
mempool_api = self.object.labelbase.get_mempool_api()
context["res_tx"] = mempool_api.get_transaction(self.object.ref)
if self.object.type == "output":
context["output"] = OutputStat.objects.filter(type_ref_hash= \
self.object.type_ref_hash).last()
return context
class LabelbaseUpdateView(UpdateView):
def post(self, request, *args, **kwargs):
labelbase_id = self.kwargs["labelbase_id"]
labelbase = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
labelbase.name = request.POST.get("name", "")
labelbase.fingerprint = request.POST.get("fingerprint", "")
labelbase.about = request.POST.get("about", "")
labelbase.operation_mode = request.POST.get("operation_mode", "")
labelbase.network = request.POST.get("network", "")
labelbase.save()
return HttpResponseRedirect(labelbase.get_absolute_url())
class LabelbaseFormView(FormView):
template_name = "labelbase_new.html"
form_class = LabelbaseForm
def get(self, request, *args, **kwargs):
return redirect(resolve_url("home"))
def form_valid(self, form):
form.instance.user = self.request.user
form.save()
return redirect(form.instance.get_absolute_url())
class RegistrationView(FormView):
template_name = "registration.html"
form_class = UserCreationForm
def form_valid(self, form):
form.save()
return redirect("registration_complete")
class RegistrationCompleteView(TemplateView):
template_name = "registration_complete.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["login_url"] = resolve_url(settings.LOGIN_URL)
return context
@class_view_decorator(never_cache)
class ExampleSecretView(OTPRequiredMixin, TemplateView):
template_name = "secret.html"

16
django/labellabor/wsgi.py Normal file
View file

@ -0,0 +1,16 @@
"""
WSGI config for labellabor 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.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "labellabor.settings")
application = get_wsgi_application()