From 33eb31f86ee2ae2822af645d3d8a9fa2d894ca3e Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 8 Oct 2023 18:01:23 +0800 Subject: [PATCH] logs system --- .gitignore | 3 +- budget/settings.py | 3 ++ expenses/admin.py | 34 +++++++++++- ...005_expense_cash_historicalexpense_cash.py | 23 ++++++++ .../migrations/0006_logs_historicallogs.py | 52 +++++++++++++++++++ ...r_historicallogs_title_alter_logs_title.py | 23 ++++++++ ..._historicallog_rename_logs_log_and_more.py | 27 ++++++++++ ...dness_value_log_goodness_value_and_more.py | 28 ++++++++++ expenses/models.py | 30 ++++++++++- expenses/static/logs.css | 12 +++++ 10 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 expenses/migrations/0005_expense_cash_historicalexpense_cash.py create mode 100644 expenses/migrations/0006_logs_historicallogs.py create mode 100644 expenses/migrations/0007_alter_historicallogs_title_alter_logs_title.py create mode 100644 expenses/migrations/0008_rename_historicallogs_historicallog_rename_logs_log_and_more.py create mode 100644 expenses/migrations/0009_historicallog_goodness_value_log_goodness_value_and_more.py create mode 100644 expenses/static/logs.css diff --git a/.gitignore b/.gitignore index f1bfebd..278d8c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,13 @@ # nah i don't think i want you to have my receipts thanks... receipts/ receipts/* +db_backup/ etc/ test.py localhost_config.py -static/ +/static/ # Byte-compiled / optimized / DLL files diff --git a/budget/settings.py b/budget/settings.py index f2ac41c..d9a6978 100644 --- a/budget/settings.py +++ b/budget/settings.py @@ -10,6 +10,7 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/4.2/ref/settings/ """ +import os from import_export.formats.base_formats import CSV, XLSX from pathlib import Path @@ -135,3 +136,5 @@ STATIC_URL = "static/" # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") diff --git a/expenses/admin.py b/expenses/admin.py index 690311e..9a1ece6 100644 --- a/expenses/admin.py +++ b/expenses/admin.py @@ -5,7 +5,7 @@ from django.contrib import admin from util import next_payday, rgb_tuple_to_hex from .admin_base import AdminBase, DeletedListFilter, DeletableAdminForm -from .models import Expense, ExpenseCategory, Timesheet, TimesheetRate, Vendor +from .models import Log, Expense, ExpenseCategory, Timesheet, TimesheetRate, Vendor from django import forms from django.utils import timezone from import_export import resources @@ -13,6 +13,38 @@ from import_export.admin import ImportExportModelAdmin from admincharts.admin import AdminChartMixin from admincharts.utils import months_between_dates from djmoney.contrib.exchange.models import convert_money +from django.utils.html import format_html + + +class LogsAdminForm(DeletableAdminForm): + class Meta: + model = Log + fields = "__all__" + + +@admin.register(Log) +class LogsAdmin(AdminBase): + class Media: + css = {"all": ("/static/logs.css",)} + + def truncated_title(self, instance): + return f"{instance.title[:2]}...{instance.title[-1:]}" + + list_display = ( + "title", + "truncated_title", + "date", + "goodness_value", + ) + list_display_links = ( + "title", + "truncated_title", + ) + search_fields = ( + "title", + "date", + ) + form = LogsAdminForm class VendorAdminForm(DeletableAdminForm): diff --git a/expenses/migrations/0005_expense_cash_historicalexpense_cash.py b/expenses/migrations/0005_expense_cash_historicalexpense_cash.py new file mode 100644 index 0000000..054c6ea --- /dev/null +++ b/expenses/migrations/0005_expense_cash_historicalexpense_cash.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.5 on 2023-10-04 10:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('expenses', '0004_vendor_remove_expense_merchant_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='expense', + name='cash', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='historicalexpense', + name='cash', + field=models.BooleanField(default=False), + ), + ] diff --git a/expenses/migrations/0006_logs_historicallogs.py b/expenses/migrations/0006_logs_historicallogs.py new file mode 100644 index 0000000..3d107c3 --- /dev/null +++ b/expenses/migrations/0006_logs_historicallogs.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.5 on 2023-10-06 13:43 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import simple_history.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('expenses', '0005_expense_cash_historicalexpense_cash'), + ] + + operations = [ + migrations.CreateModel( + name='Logs', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('deleted', models.BooleanField(default=False)), + ('title', models.TextField(blank=True)), + ('date', models.DateTimeField(blank=True, null=True)), + ('information', models.TextField(blank=True)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='HistoricalLogs', + fields=[ + ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')), + ('deleted', models.BooleanField(default=False)), + ('title', models.TextField(blank=True)), + ('date', models.DateTimeField(blank=True, null=True)), + ('information', models.TextField(blank=True)), + ('history_id', models.AutoField(primary_key=True, serialize=False)), + ('history_date', models.DateTimeField(db_index=True)), + ('history_change_reason', models.CharField(max_length=100, null=True)), + ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)), + ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'historical logs', + 'verbose_name_plural': 'historical logss', + 'ordering': ('-history_date', '-history_id'), + 'get_latest_by': ('history_date', 'history_id'), + }, + bases=(simple_history.models.HistoricalChanges, models.Model), + ), + ] diff --git a/expenses/migrations/0007_alter_historicallogs_title_alter_logs_title.py b/expenses/migrations/0007_alter_historicallogs_title_alter_logs_title.py new file mode 100644 index 0000000..3088a09 --- /dev/null +++ b/expenses/migrations/0007_alter_historicallogs_title_alter_logs_title.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.5 on 2023-10-06 13:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('expenses', '0006_logs_historicallogs'), + ] + + operations = [ + migrations.AlterField( + model_name='historicallogs', + name='title', + field=models.CharField(blank=True, max_length=256), + ), + migrations.AlterField( + model_name='logs', + name='title', + field=models.CharField(blank=True, max_length=256), + ), + ] diff --git a/expenses/migrations/0008_rename_historicallogs_historicallog_rename_logs_log_and_more.py b/expenses/migrations/0008_rename_historicallogs_historicallog_rename_logs_log_and_more.py new file mode 100644 index 0000000..7b2a5c8 --- /dev/null +++ b/expenses/migrations/0008_rename_historicallogs_historicallog_rename_logs_log_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.5 on 2023-10-06 13:49 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('expenses', '0007_alter_historicallogs_title_alter_logs_title'), + ] + + operations = [ + migrations.RenameModel( + old_name='HistoricalLogs', + new_name='HistoricalLog', + ), + migrations.RenameModel( + old_name='Logs', + new_name='Log', + ), + migrations.AlterModelOptions( + name='historicallog', + options={'get_latest_by': ('history_date', 'history_id'), 'ordering': ('-history_date', '-history_id'), 'verbose_name': 'historical log', 'verbose_name_plural': 'historical logs'}, + ), + ] diff --git a/expenses/migrations/0009_historicallog_goodness_value_log_goodness_value_and_more.py b/expenses/migrations/0009_historicallog_goodness_value_log_goodness_value_and_more.py new file mode 100644 index 0000000..71fa624 --- /dev/null +++ b/expenses/migrations/0009_historicallog_goodness_value_log_goodness_value_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.5 on 2023-10-08 09:55 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('expenses', '0008_rename_historicallogs_historicallog_rename_logs_log_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='historicallog', + name='goodness_value', + field=models.FloatField(default=0.0, validators=[django.core.validators.MinValueValidator(-10.0), django.core.validators.MaxValueValidator(10.0)]), + ), + migrations.AddField( + model_name='log', + name='goodness_value', + field=models.FloatField(default=0.0, validators=[django.core.validators.MinValueValidator(-10.0), django.core.validators.MaxValueValidator(10.0)]), + ), + migrations.AddConstraint( + model_name='log', + constraint=models.CheckConstraint(check=models.Q(('goodness_value__gte', -10.0), ('goodness_value__lte', 10.0)), name='log_goodness_value_range'), + ), + ] diff --git a/expenses/models.py b/expenses/models.py index 00ae59e..4314c3e 100644 --- a/expenses/models.py +++ b/expenses/models.py @@ -4,7 +4,8 @@ from django.utils import timezone from djmoney.models.fields import MoneyField, Money from .deletable_model import DeletableModel from util import truncate_string # new line -from django.db.models import Q +from django.db.models import Q, CheckConstraint +from django.core.validators import MaxValueValidator, MinValueValidator # class ExpenseManager(models.Manager): # def active(self): @@ -30,6 +31,32 @@ from django.db.models import Q # actions = [soft_delete_selected] +class Log(DeletableModel): + # TODO: Use Markdown formatting! + title = models.CharField(max_length=256, blank=True) + date = models.DateTimeField(null=True, blank=True) + goodness_value = models.FloatField( + default=0.0, + validators=( + MinValueValidator(-10.0), + MaxValueValidator(10.0), + ), + ) + information = models.TextField(blank=True) + + def __str__(self): + return f"{self.date}: {self.title}" + + class Meta: + constraints = ( + # for checking in the DB + CheckConstraint( + check=Q(goodness_value__gte=-10.0) & Q(goodness_value__lte=10.0), + name="log_goodness_value_range", + ), + ) + + class TimesheetRate(DeletableModel): rate = MoneyField( decimal_places=3, @@ -141,6 +168,7 @@ class Expense(DeletableModel): blank=True, related_name="expenses", ) + cash = models.BooleanField(default=False) def __str__(self): return f"{self.date} {self.price} {truncate_string(self.description,32)}" diff --git a/expenses/static/logs.css b/expenses/static/logs.css new file mode 100644 index 0000000..798bda4 --- /dev/null +++ b/expenses/static/logs.css @@ -0,0 +1,12 @@ +/* Hide title of log when mouse is not hovering over it for privacy */ +.field-title:not(:hover) > a { + color: transparent; + text-decoration: none; + cursor: pointer; +} + +.field-goodness_value:not(:hover) { + color: transparent; + text-decoration: none; + cursor: pointer; +}