mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 11:06:40 +07:00
feat: add free-form tags on transactions
Third of the four ideas picked from the Maybe/Sure feature audit. Tags are orthogonal to categories (category = what kind of spend, tag = which event/project) — many-to-many, created inline while typing in the transaction form, filterable from the Transactions page. Also adds tests/test_backup.py, the first test coverage the backup export/import path has ever had, and fixes a MissingGreenlet bug it surfaced in restore_backup's tag-wiring. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,13 @@ class CategoryBackup(BaseModel):
|
||||
parent_id: int | None = None
|
||||
|
||||
|
||||
class TagBackup(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
name: str
|
||||
|
||||
|
||||
class TransactionBackup(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -55,6 +62,11 @@ class TransactionBackup(BaseModel):
|
||||
merchant: str | None
|
||||
notes: str | None
|
||||
date: date_
|
||||
# Defaulted so a backup exported before tags existed still imports
|
||||
# cleanly under the same format version. Not a plain column — populated
|
||||
# explicitly in build_backup() from the `tags` relationship, since
|
||||
# from_attributes can't map a `tags` relationship to a `tag_ids` field.
|
||||
tag_ids: list[int] = Field(default_factory=list)
|
||||
|
||||
|
||||
class AssetBackup(BaseModel):
|
||||
@@ -155,6 +167,9 @@ class BackupPayload(BaseModel):
|
||||
app_version: str
|
||||
accounts: list[AccountBackup]
|
||||
categories: list[CategoryBackup]
|
||||
# Defaulted so a backup exported before tags existed still imports
|
||||
# cleanly under the same format version.
|
||||
tags: list[TagBackup] = Field(default_factory=list)
|
||||
transactions: list[TransactionBackup]
|
||||
assets: list[AssetBackup]
|
||||
asset_valuations: list[AssetValuationBackup]
|
||||
|
||||
12
backend/app/schemas/tag.py
Normal file
12
backend/app/schemas/tag.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class TagCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=50)
|
||||
|
||||
|
||||
class TagRead(BaseModel):
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
id: int
|
||||
name: str
|
||||
@@ -7,6 +7,7 @@ from app.core.text import capitalize_first_letter
|
||||
from app.models.enums import TransactionType
|
||||
from app.schemas.account import AccountRead
|
||||
from app.schemas.category import CategoryRead
|
||||
from app.schemas.tag import TagRead
|
||||
|
||||
|
||||
class TransactionBase(BaseModel):
|
||||
@@ -41,7 +42,7 @@ class TransactionBase(BaseModel):
|
||||
|
||||
|
||||
class TransactionCreate(TransactionBase):
|
||||
pass
|
||||
tag_ids: list[int] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TransactionUpdate(BaseModel):
|
||||
@@ -54,6 +55,8 @@ class TransactionUpdate(BaseModel):
|
||||
merchant: str | None = Field(default=None, max_length=150)
|
||||
notes: str | None = None
|
||||
date: date_ | None = None
|
||||
# Omitted -> tags untouched; sent (even as []) -> replaces the full tag set.
|
||||
tag_ids: list[int] | None = None
|
||||
|
||||
@field_validator("description")
|
||||
@classmethod
|
||||
@@ -67,6 +70,7 @@ class TransactionRead(TransactionBase):
|
||||
id: int
|
||||
account: AccountRead
|
||||
category: CategoryRead | None = None
|
||||
tags: list[TagRead] = Field(default_factory=list)
|
||||
|
||||
|
||||
class TransactionPage(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user