mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-03-23 18:01:16 +01:00
feat(transactions): soft delete
This commit is contained in:
@@ -12,7 +12,14 @@ from apps.transactions.models import (
|
||||
|
||||
@admin.register(Transaction)
|
||||
class TransactionModelAdmin(admin.ModelAdmin):
|
||||
def get_queryset(self, request):
|
||||
# Use the all_objects manager to show all transactions, including deleted ones
|
||||
return self.model.all_objects.all()
|
||||
|
||||
list_filter = ["deleted", "type", "is_paid", "date", "account"]
|
||||
|
||||
list_display = [
|
||||
"deleted",
|
||||
"description",
|
||||
"type",
|
||||
"account__name",
|
||||
@@ -22,6 +29,17 @@ class TransactionModelAdmin(admin.ModelAdmin):
|
||||
"reference_date",
|
||||
]
|
||||
|
||||
actions = ["hard_delete_selected"]
|
||||
|
||||
def hard_delete_selected(self, request, queryset):
|
||||
for obj in queryset:
|
||||
obj.hard_delete()
|
||||
self.message_user(
|
||||
request, f"Successfully hard deleted {queryset.count()} transactions."
|
||||
)
|
||||
|
||||
hard_delete_selected.short_description = "Hard delete selected transactions"
|
||||
|
||||
|
||||
class TransactionInline(admin.TabularInline):
|
||||
model = Transaction
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-19 00:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('transactions', '0027_alter_transaction_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='internal_note',
|
||||
field=models.TextField(blank=True, verbose_name='Internal Note'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-19 14:59
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('transactions', '0028_transaction_internal_note'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='transaction',
|
||||
options={'default_manager_name': 'objects', 'verbose_name': 'Transaction', 'verbose_name_plural': 'Transactions'},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-19 14:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('transactions', '0029_alter_transaction_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='deleted',
|
||||
field=models.BooleanField(default=False, verbose_name='Deleted'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='deleted_at',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-19 15:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('transactions', '0030_transaction_deleted_transaction_deleted_at'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='transaction',
|
||||
name='deleted',
|
||||
field=models.BooleanField(db_index=True, default=False, verbose_name='Deleted'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.1.5 on 2025-01-19 16:48
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('transactions', '0031_alter_transaction_deleted'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='updated_at',
|
||||
field=models.DateTimeField(auto_now=True),
|
||||
),
|
||||
]
|
||||
@@ -6,6 +6,7 @@ from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from apps.common.fields.month_year import MonthYearModelField
|
||||
from apps.common.functions.decimals import truncate_decimal
|
||||
@@ -15,6 +16,53 @@ from apps.transactions.validators import validate_decimal_places, validate_non_n
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
class SoftDeleteQuerySet(models.QuerySet):
|
||||
def delete(self):
|
||||
if not settings.ENABLE_SOFT_DELETION:
|
||||
# If soft deletion is disabled, perform a normal delete
|
||||
return super().delete()
|
||||
|
||||
# Separate the queryset into already deleted and not deleted objects
|
||||
already_deleted = self.filter(deleted=True)
|
||||
not_deleted = self.filter(deleted=False)
|
||||
|
||||
# Use a transaction to ensure atomicity
|
||||
with transaction.atomic():
|
||||
# Perform hard delete on already deleted objects
|
||||
hard_deleted_count = already_deleted._raw_delete(already_deleted.db)
|
||||
|
||||
# Perform soft delete on not deleted objects
|
||||
soft_deleted_count = not_deleted.update(
|
||||
deleted=True, deleted_at=timezone.now()
|
||||
)
|
||||
|
||||
# Return a tuple of counts as expected by Django's delete method
|
||||
return (
|
||||
hard_deleted_count + soft_deleted_count,
|
||||
{"Transaction": hard_deleted_count + soft_deleted_count},
|
||||
)
|
||||
|
||||
def hard_delete(self):
|
||||
return super().delete()
|
||||
|
||||
|
||||
class SoftDeleteManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
qs = SoftDeleteQuerySet(self.model, using=self._db)
|
||||
return qs if not settings.ENABLE_SOFT_DELETION else qs.filter(deleted=False)
|
||||
|
||||
|
||||
class AllObjectsManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return SoftDeleteQuerySet(self.model, using=self._db)
|
||||
|
||||
|
||||
class DeletedObjectsManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
qs = SoftDeleteQuerySet(self.model, using=self._db)
|
||||
return qs if not settings.ENABLE_SOFT_DELETION else qs.filter(deleted=True)
|
||||
|
||||
|
||||
class TransactionCategory(models.Model):
|
||||
name = models.CharField(max_length=255, verbose_name=_("Name"), unique=True)
|
||||
mute = models.BooleanField(default=False, verbose_name=_("Mute"))
|
||||
@@ -143,10 +191,24 @@ class Transaction(models.Model):
|
||||
)
|
||||
internal_note = models.TextField(blank=True, verbose_name=_("Internal Note"))
|
||||
|
||||
deleted = models.BooleanField(
|
||||
default=False, verbose_name=_("Deleted"), db_index=True
|
||||
)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
deleted_at = models.DateTimeField(
|
||||
null=True, blank=True, verbose_name=_("Deleted At")
|
||||
)
|
||||
|
||||
objects = SoftDeleteManager.from_queryset(SoftDeleteQuerySet)()
|
||||
all_objects = AllObjectsManager.from_queryset(SoftDeleteQuerySet)()
|
||||
deleted_objects = DeletedObjectsManager.from_queryset(SoftDeleteQuerySet)()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Transaction")
|
||||
verbose_name_plural = _("Transactions")
|
||||
db_table = "transactions"
|
||||
default_manager_name = "objects"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.amount = truncate_decimal(
|
||||
@@ -161,6 +223,17 @@ class Transaction(models.Model):
|
||||
self.full_clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
if settings.ENABLE_SOFT_DELETION:
|
||||
self.deleted = True
|
||||
self.deleted_at = timezone.now()
|
||||
self.save()
|
||||
else:
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
def hard_delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
def exchanged_amount(self):
|
||||
if self.account.exchange_currency:
|
||||
converted_amount, prefix, suffix, decimal_places = convert(
|
||||
@@ -179,6 +252,10 @@ class Transaction(models.Model):
|
||||
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
type_display = self.get_type_display()
|
||||
return f"{self.description} - {type_display} - {self.account} - {self.date}"
|
||||
|
||||
|
||||
class InstallmentPlan(models.Model):
|
||||
class Recurrence(models.TextChoices):
|
||||
|
||||
Reference in New Issue
Block a user