mirror of
https://github.com/peter-tanner/money-manager.git
synced 2024-11-30 14:20:17 +08:00
Redirect / -> /admin/, better random color function
This commit is contained in:
parent
33c3e42864
commit
0a5c93a3c4
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,3 +1,8 @@
|
||||||
|
# nah i don't think i want you to have my receipts thanks...
|
||||||
|
receipts/
|
||||||
|
receipts/*
|
||||||
|
|
||||||
|
|
||||||
etc/
|
etc/
|
||||||
test.py
|
test.py
|
||||||
localhost_config.py
|
localhost_config.py
|
||||||
|
|
|
@ -17,11 +17,12 @@ Including another URLconf
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path, reverse_lazy
|
||||||
from expenses.models import Expense, Timesheet
|
from expenses.models import Expense, Timesheet
|
||||||
|
from django.views.generic.base import RedirectView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("", RedirectView.as_view(url=reverse_lazy("admin:index"))),
|
||||||
path("admin/expenses/", include("expenses.admin_urls")),
|
path("admin/expenses/", include("expenses.admin_urls")),
|
||||||
path("admin/summary/", include("summary.urls")),
|
path("admin/summary/", include("summary.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
|
|
|
@ -1,21 +1,13 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
import random
|
import distinctipy
|
||||||
from typing import Any, Dict
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.admin.options import ModelAdmin
|
|
||||||
from django.core.handlers.wsgi import WSGIRequest
|
|
||||||
from django.db.models.base import Model
|
|
||||||
from django.db.models.fields import Field
|
|
||||||
from django.forms import DecimalField, TextInput, NumberInput
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from util import next_payday
|
from util import next_payday, rgb_tuple_to_hex
|
||||||
|
|
||||||
from .admin_base import AdminBase, DeletedListFilter, DeletableAdminForm
|
from .admin_base import AdminBase, DeletedListFilter, DeletableAdminForm
|
||||||
from .models import Expense, ExpenseCategory, Timesheet, TimesheetRate
|
from .models import Expense, ExpenseCategory, Timesheet, TimesheetRate, Vendor
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.contrib.admin.widgets import AdminDateWidget, AdminSplitDateTime
|
|
||||||
from import_export import resources
|
from import_export import resources
|
||||||
from import_export.admin import ImportExportModelAdmin
|
from import_export.admin import ImportExportModelAdmin
|
||||||
from admincharts.admin import AdminChartMixin
|
from admincharts.admin import AdminChartMixin
|
||||||
|
@ -23,6 +15,22 @@ from admincharts.utils import months_between_dates
|
||||||
from djmoney.contrib.exchange.models import convert_money
|
from djmoney.contrib.exchange.models import convert_money
|
||||||
|
|
||||||
|
|
||||||
|
class VendorAdminForm(DeletableAdminForm):
|
||||||
|
class Meta:
|
||||||
|
model = Vendor
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Vendor)
|
||||||
|
class VendorAdmin(AdminBase):
|
||||||
|
list_display = (
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
)
|
||||||
|
search_fields = ("name",)
|
||||||
|
form = VendorAdminForm
|
||||||
|
|
||||||
|
|
||||||
class TimesheetRateAdminForm(DeletableAdminForm):
|
class TimesheetRateAdminForm(DeletableAdminForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = TimesheetRate
|
model = TimesheetRate
|
||||||
|
@ -169,8 +177,9 @@ class ExpenseAdminForm(DeletableAdminForm):
|
||||||
class ExpenseAdmin(AdminBase, AdminChartMixin, ImportExportModelAdmin):
|
class ExpenseAdmin(AdminBase, AdminChartMixin, ImportExportModelAdmin):
|
||||||
form = ExpenseAdminForm
|
form = ExpenseAdminForm
|
||||||
|
|
||||||
list_display = ("date", "price", "description", "category")
|
autocomplete_fields = ("vendor", "category")
|
||||||
list_filter = ("date", ("deleted", DeletedListFilter), "category")
|
list_display = ("date", "price", "description", "category", "vendor")
|
||||||
|
list_filter = ("date", ("deleted", DeletedListFilter), "category", "vendor")
|
||||||
ordering = ("-date", "-deleted")
|
ordering = ("-date", "-deleted")
|
||||||
readonly_fields = ("deleted",)
|
readonly_fields = ("deleted",)
|
||||||
|
|
||||||
|
@ -197,14 +206,22 @@ class ExpenseAdmin(AdminBase, AdminChartMixin, ImportExportModelAdmin):
|
||||||
labels = []
|
labels = []
|
||||||
totals = []
|
totals = []
|
||||||
expenses_total = []
|
expenses_total = []
|
||||||
expenses = {
|
expense_types = set([x.category.name for x in queryset])
|
||||||
k: {
|
colors = distinctipy.get_colors(len(expense_types), pastel_factor=0.2, rng=69)
|
||||||
"label": k,
|
expenses = {}
|
||||||
"data": [],
|
i = 0
|
||||||
"backgroundColor": f"#{random.Random(x=1).randrange(0x1000000):06x}",
|
for k in expense_types:
|
||||||
}
|
expenses.update(
|
||||||
for k in set([x.category.name for x in queryset])
|
{
|
||||||
}
|
k: {
|
||||||
|
"label": k,
|
||||||
|
"data": [],
|
||||||
|
"backgroundColor": rgb_tuple_to_hex(colors[i]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
i += 1
|
||||||
|
|
||||||
for b in months_between_dates(earliest, timezone.now().date()):
|
for b in months_between_dates(earliest, timezone.now().date()):
|
||||||
labels.append(b.strftime("%b %Y"))
|
labels.append(b.strftime("%b %Y"))
|
||||||
totals.append(
|
totals.append(
|
||||||
|
@ -213,7 +230,7 @@ class ExpenseAdmin(AdminBase, AdminChartMixin, ImportExportModelAdmin):
|
||||||
convert_money(x.total, "AUD").amount
|
convert_money(x.total, "AUD").amount
|
||||||
for x in timesheets
|
for x in timesheets
|
||||||
if x.shift_start.year == b.year
|
if x.shift_start.year == b.year
|
||||||
and x.shift_start.month == b.month
|
and x.shift_start.month == b.month # noqa
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -241,10 +258,10 @@ class ExpenseAdmin(AdminBase, AdminChartMixin, ImportExportModelAdmin):
|
||||||
"backgroundColor": "#865137",
|
"backgroundColor": "#865137",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
+ list(expenses.values()),
|
+ list(expenses.values()), # noqa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ExpenseCategory)
|
@admin.register(ExpenseCategory)
|
||||||
class ExpenseCategoryAdmin(AdminBase):
|
class ExpenseCategoryAdmin(AdminBase):
|
||||||
pass
|
search_fields = ("name",)
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Generated by Django 4.2.5 on 2023-09-21 04:31
|
||||||
|
|
||||||
|
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', '0003_remove_expensecategory_default_rate_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Vendor',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('deleted', models.BooleanField(default=False)),
|
||||||
|
('name', models.TextField(blank=True)),
|
||||||
|
('description', models.TextField(blank=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='expense',
|
||||||
|
name='merchant',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalexpense',
|
||||||
|
name='merchant',
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='HistoricalVendor',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||||
|
('deleted', models.BooleanField(default=False)),
|
||||||
|
('name', models.TextField(blank=True)),
|
||||||
|
('description', 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 vendor',
|
||||||
|
'verbose_name_plural': 'historical vendors',
|
||||||
|
'ordering': ('-history_date', '-history_id'),
|
||||||
|
'get_latest_by': ('history_date', 'history_id'),
|
||||||
|
},
|
||||||
|
bases=(simple_history.models.HistoricalChanges, models.Model),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='expense',
|
||||||
|
name='vendor',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='expenses', to='expenses.vendor'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='historicalexpense',
|
||||||
|
name='vendor',
|
||||||
|
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='expenses.vendor'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -108,6 +108,14 @@ class ExpenseCategory(DeletableModel):
|
||||||
return truncate_string(self.name, 48)
|
return truncate_string(self.name, 48)
|
||||||
|
|
||||||
|
|
||||||
|
class Vendor(DeletableModel):
|
||||||
|
name = models.TextField(blank=True)
|
||||||
|
description = models.TextField(blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return truncate_string(self.name, 48)
|
||||||
|
|
||||||
|
|
||||||
class Expense(DeletableModel):
|
class Expense(DeletableModel):
|
||||||
price = MoneyField(
|
price = MoneyField(
|
||||||
decimal_places=3,
|
decimal_places=3,
|
||||||
|
@ -117,7 +125,13 @@ class Expense(DeletableModel):
|
||||||
)
|
)
|
||||||
date = models.DateField()
|
date = models.DateField()
|
||||||
description = models.TextField(blank=True)
|
description = models.TextField(blank=True)
|
||||||
merchant = models.CharField(max_length=255, blank=True)
|
vendor = models.ForeignKey(
|
||||||
|
Vendor,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="expenses",
|
||||||
|
)
|
||||||
receipt = models.FileField(upload_to="receipts/", blank=True)
|
receipt = models.FileField(upload_to="receipts/", blank=True)
|
||||||
link = models.URLField(blank=True)
|
link = models.URLField(blank=True)
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
|
|
|
@ -3,15 +3,15 @@ Babel==2.12.1
|
||||||
backports.zoneinfo==0.2.1
|
backports.zoneinfo==0.2.1
|
||||||
defusedxml==0.7.1
|
defusedxml==0.7.1
|
||||||
diff-match-patch==20230430
|
diff-match-patch==20230430
|
||||||
|
distinctipy==1.2.3
|
||||||
Django==4.2.5
|
Django==4.2.5
|
||||||
django-admincharts==0.4.1
|
django-admincharts==0.4.1
|
||||||
django-computedfields==0.2.3
|
|
||||||
django-fast-update==0.2.3
|
|
||||||
django-import-export==3.3.1
|
django-import-export==3.3.1
|
||||||
django-money==3.2.0
|
django-money==3.2.0
|
||||||
django-simple-history==3.4.0
|
django-simple-history==3.4.0
|
||||||
et-xmlfile==1.1.0
|
et-xmlfile==1.1.0
|
||||||
MarkupPy==1.14
|
MarkupPy==1.14
|
||||||
|
numpy==1.24.4
|
||||||
odfpy==1.4.1
|
odfpy==1.4.1
|
||||||
openpyxl==3.1.2
|
openpyxl==3.1.2
|
||||||
pip-autoremove==0.10.0
|
pip-autoremove==0.10.0
|
||||||
|
|
8
util.py
8
util.py
|
@ -33,3 +33,11 @@ def next_payday(day=datetime.now()) -> datetime:
|
||||||
next_payday -= timedelta(days=1)
|
next_payday -= timedelta(days=1)
|
||||||
|
|
||||||
return next_payday
|
return next_payday
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_tuple_to_hex(rgb_tuple):
|
||||||
|
# Ensure the values are within the valid range
|
||||||
|
color = [min(max(0, x), 1) for x in rgb_tuple]
|
||||||
|
color = [format(int(x * 255), "02x") for x in color]
|
||||||
|
rgb_hex = f"#{color[0]}{color[1]}{color[2]}"
|
||||||
|
return rgb_hex
|
||||||
|
|
Loading…
Reference in New Issue
Block a user