mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-04-24 09:38:35 +02:00
feat(transactions): soft delete
This commit is contained in:
@@ -12,7 +12,14 @@ from apps.transactions.models import (
|
|||||||
|
|
||||||
@admin.register(Transaction)
|
@admin.register(Transaction)
|
||||||
class TransactionModelAdmin(admin.ModelAdmin):
|
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 = [
|
list_display = [
|
||||||
|
"deleted",
|
||||||
"description",
|
"description",
|
||||||
"type",
|
"type",
|
||||||
"account__name",
|
"account__name",
|
||||||
@@ -22,6 +29,17 @@ class TransactionModelAdmin(admin.ModelAdmin):
|
|||||||
"reference_date",
|
"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):
|
class TransactionInline(admin.TabularInline):
|
||||||
model = Transaction
|
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.db.models import Q
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
from apps.common.fields.month_year import MonthYearModelField
|
from apps.common.fields.month_year import MonthYearModelField
|
||||||
from apps.common.functions.decimals import truncate_decimal
|
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()
|
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):
|
class TransactionCategory(models.Model):
|
||||||
name = models.CharField(max_length=255, verbose_name=_("Name"), unique=True)
|
name = models.CharField(max_length=255, verbose_name=_("Name"), unique=True)
|
||||||
mute = models.BooleanField(default=False, verbose_name=_("Mute"))
|
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"))
|
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:
|
class Meta:
|
||||||
verbose_name = _("Transaction")
|
verbose_name = _("Transaction")
|
||||||
verbose_name_plural = _("Transactions")
|
verbose_name_plural = _("Transactions")
|
||||||
db_table = "transactions"
|
db_table = "transactions"
|
||||||
|
default_manager_name = "objects"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.amount = truncate_decimal(
|
self.amount = truncate_decimal(
|
||||||
@@ -161,6 +223,17 @@ class Transaction(models.Model):
|
|||||||
self.full_clean()
|
self.full_clean()
|
||||||
super().save(*args, **kwargs)
|
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):
|
def exchanged_amount(self):
|
||||||
if self.account.exchange_currency:
|
if self.account.exchange_currency:
|
||||||
converted_amount, prefix, suffix, decimal_places = convert(
|
converted_amount, prefix, suffix, decimal_places = convert(
|
||||||
@@ -179,6 +252,10 @@ class Transaction(models.Model):
|
|||||||
|
|
||||||
return None
|
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 InstallmentPlan(models.Model):
|
||||||
class Recurrence(models.TextChoices):
|
class Recurrence(models.TextChoices):
|
||||||
|
|||||||
Reference in New Issue
Block a user