mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
* Add CustomLogEntry model support and update tests: - Added support for CustomLogEntry data model to extend django-auditlog capabilities - Updated existing test cases to align with new model structure and data handling logic - Added new test cases to validate CustomLogEntry behavior, model registration, and signal handling - Ensured backward compatibility with existing LogEntry model where applicable * Update auditlog/__init__.py Co-authored-by: Youngkwang Yang <me@youngkwang.dev> * run only one custom model test matrix (#761) --------- Co-authored-by: Youngkwang Yang <me@youngkwang.dev>
146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
from math import ceil
|
|
|
|
from django.conf import settings
|
|
from django.core.management import CommandError, CommandParser
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from auditlog import get_logentry_model
|
|
|
|
LogEntry = get_logentry_model()
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Migrates changes from changes_text to json changes."
|
|
requires_migrations_checks = True
|
|
|
|
def add_arguments(self, parser: CommandParser):
|
|
group = parser.add_argument_group()
|
|
group.add_argument(
|
|
"--check",
|
|
action="store_true",
|
|
help="Just check the status of the migration",
|
|
dest="check",
|
|
)
|
|
group.add_argument(
|
|
"-d",
|
|
"--database",
|
|
default=None,
|
|
metavar="The database engine",
|
|
help="If provided, the script will use native db operations. "
|
|
"Otherwise, it will use LogEntry.objects.bulk_update",
|
|
dest="db",
|
|
type=str,
|
|
choices=["postgres", "mysql", "oracle"],
|
|
)
|
|
group.add_argument(
|
|
"-b",
|
|
"--batch-size",
|
|
default=500,
|
|
help="Split the migration into multiple batches. If 0, then no batching will be done. "
|
|
"When passing a -d/database, the batch value will be ignored.",
|
|
dest="batch_size",
|
|
type=int,
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
database = options["db"]
|
|
batch_size = options["batch_size"]
|
|
check = options["check"]
|
|
|
|
if (not self.check_logs()) or check:
|
|
return
|
|
|
|
if database:
|
|
result = self.migrate_using_sql(database)
|
|
self.stdout.write(
|
|
self.style.SUCCESS(
|
|
f"Updated {result} records using native database operations."
|
|
)
|
|
)
|
|
else:
|
|
result = self.migrate_using_django(batch_size)
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"Updated {result} records using django operations.")
|
|
)
|
|
|
|
self.check_logs()
|
|
|
|
def check_logs(self):
|
|
count = self.get_logs().count()
|
|
if count:
|
|
self.stdout.write(f"There are {count} records that needs migration.")
|
|
return True
|
|
|
|
self.stdout.write(self.style.SUCCESS("All records have been migrated."))
|
|
if settings.AUDITLOG_USE_TEXT_CHANGES_IF_JSON_IS_NOT_PRESENT:
|
|
var_msg = self.style.WARNING(
|
|
"AUDITLOG_USE_TEXT_CHANGES_IF_JSON_IS_NOT_PRESENT"
|
|
)
|
|
self.stdout.write(f"You can now set {var_msg} to False.")
|
|
|
|
return False
|
|
|
|
def get_logs(self):
|
|
return LogEntry.objects.filter(
|
|
changes_text__isnull=False, changes__isnull=True
|
|
).exclude(changes_text__exact="")
|
|
|
|
def migrate_using_django(self, batch_size):
|
|
def _apply_django_migration(_logs) -> int:
|
|
import json
|
|
|
|
updated = []
|
|
errors = []
|
|
for log in _logs:
|
|
try:
|
|
log.changes = json.loads(log.changes_text)
|
|
except ValueError:
|
|
errors.append(log.id)
|
|
else:
|
|
updated.append(log)
|
|
|
|
LogEntry.objects.bulk_update(updated, fields=["changes"])
|
|
if errors:
|
|
self.stderr.write(
|
|
self.style.ERROR(
|
|
f"ValueError was raised while converting the logs with these ids into json."
|
|
f"They where not be included in this migration batch."
|
|
f"\n"
|
|
f"{errors}"
|
|
)
|
|
)
|
|
return len(updated)
|
|
|
|
logs = self.get_logs()
|
|
|
|
if not batch_size:
|
|
return _apply_django_migration(logs)
|
|
|
|
total_updated = 0
|
|
for _ in range(ceil(logs.count() / batch_size)):
|
|
total_updated += _apply_django_migration(self.get_logs()[:batch_size])
|
|
return total_updated
|
|
|
|
def migrate_using_sql(self, database):
|
|
from django.db import connection
|
|
|
|
def postgres():
|
|
with connection.cursor() as cursor:
|
|
cursor.execute(
|
|
f"""
|
|
UPDATE {LogEntry._meta.db_table}
|
|
SET changes="changes_text"::jsonb
|
|
WHERE changes_text IS NOT NULL
|
|
AND changes_text <> ''
|
|
AND changes IS NULL
|
|
"""
|
|
)
|
|
return cursor.cursor.rowcount
|
|
|
|
if database == "postgres":
|
|
return postgres()
|
|
|
|
raise CommandError(
|
|
f"Migrating the records using {database} is not implemented. "
|
|
f"Run this management command without passing a -d/--database argument."
|
|
)
|