mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
* Add test runner and improve test with multi databases * Enhance cross-database compatibility and testing - Fix TRUNCATE command support detection for different databases - Add conditional PostgreSQL-specific model registration - Improve database-specific test skipping logic - Remove SQLite from TRUNCATE supported vendors list * Add docker compose for testing * Improve CI/CD with multi-database support - Add separate test workflows for SQLite, PostgreSQL, and MySQL * Add `mysqlclient` deps * fix minor - Add mysqlclient deps - upload coverage step * Fix coverage upload name conflicts in CI workflow - Add database type to coverage upload names (SQLite/PostgreSQL/MySQL)
487 lines
13 KiB
Python
487 lines
13 KiB
Python
import uuid
|
|
|
|
from django.conf import settings
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
from django.db import models
|
|
|
|
from auditlog.models import AuditlogHistoryField
|
|
from auditlog.registry import AuditlogModelRegistry, auditlog
|
|
|
|
m2m_only_auditlog = AuditlogModelRegistry(create=False, update=False, delete=False)
|
|
|
|
|
|
@auditlog.register()
|
|
class SimpleModel(models.Model):
|
|
"""
|
|
A simple model with no special things going on.
|
|
"""
|
|
|
|
text = models.TextField(blank=True)
|
|
boolean = models.BooleanField(default=False)
|
|
integer = models.IntegerField(blank=True, null=True)
|
|
datetime = models.DateTimeField(auto_now=True)
|
|
char = models.CharField(null=True, max_length=100, default=lambda: "default value")
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
def __str__(self):
|
|
return str(self.text)
|
|
|
|
|
|
class AltPrimaryKeyModel(models.Model):
|
|
"""
|
|
A model with a non-standard primary key.
|
|
"""
|
|
|
|
key = models.CharField(max_length=100, primary_key=True)
|
|
|
|
text = models.TextField(blank=True)
|
|
boolean = models.BooleanField(default=False)
|
|
integer = models.IntegerField(blank=True, null=True)
|
|
datetime = models.DateTimeField(auto_now=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=True, pk_indexable=False)
|
|
|
|
|
|
class UUIDPrimaryKeyModel(models.Model):
|
|
"""
|
|
A model with a UUID primary key.
|
|
"""
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
|
|
text = models.TextField(blank=True)
|
|
boolean = models.BooleanField(default=False)
|
|
integer = models.IntegerField(blank=True, null=True)
|
|
datetime = models.DateTimeField(auto_now=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=True, pk_indexable=False)
|
|
|
|
|
|
class ModelPrimaryKeyModel(models.Model):
|
|
"""
|
|
A model with another model as primary key.
|
|
"""
|
|
|
|
key = models.OneToOneField(
|
|
"SimpleModel",
|
|
primary_key=True,
|
|
on_delete=models.CASCADE,
|
|
related_name="reverse_primary_key",
|
|
)
|
|
|
|
text = models.TextField(blank=True)
|
|
boolean = models.BooleanField(default=False)
|
|
integer = models.IntegerField(blank=True, null=True)
|
|
datetime = models.DateTimeField(auto_now=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=True, pk_indexable=False)
|
|
|
|
|
|
class ProxyModel(SimpleModel):
|
|
"""
|
|
A model that is a proxy for another model.
|
|
"""
|
|
|
|
class Meta:
|
|
proxy = True
|
|
|
|
|
|
class RelatedModelParent(models.Model):
|
|
"""
|
|
Use multi table inheritance to make a OneToOneRel field
|
|
"""
|
|
|
|
|
|
class RelatedModel(RelatedModelParent):
|
|
"""
|
|
A model with a foreign key.
|
|
"""
|
|
|
|
related = models.ForeignKey(
|
|
"SimpleModel", related_name="related_models", on_delete=models.CASCADE
|
|
)
|
|
one_to_one = models.OneToOneField(
|
|
to="SimpleModel", on_delete=models.CASCADE, related_name="reverse_one_to_one"
|
|
)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
def __str__(self):
|
|
return f"RelatedModel #{self.pk} -> {self.related.id}"
|
|
|
|
|
|
class ManyRelatedModel(models.Model):
|
|
"""
|
|
A model with many-to-many relations.
|
|
"""
|
|
|
|
recursive = models.ManyToManyField("self")
|
|
related = models.ManyToManyField("ManyRelatedOtherModel", related_name="related")
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
def get_additional_data(self):
|
|
related = self.related.first()
|
|
return {"related_model_id": related.id if related else None}
|
|
|
|
|
|
class ManyRelatedOtherModel(models.Model):
|
|
"""
|
|
A model related to ManyRelatedModel as many-to-many.
|
|
"""
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
class ReusableThroughRelatedModel(models.Model):
|
|
"""
|
|
A model related to multiple other models through a model.
|
|
"""
|
|
|
|
label = models.CharField(max_length=100)
|
|
|
|
|
|
class ReusableThroughModel(models.Model):
|
|
"""
|
|
A through model that can be associated multiple different models.
|
|
"""
|
|
|
|
label = models.ForeignKey(
|
|
ReusableThroughRelatedModel,
|
|
on_delete=models.CASCADE,
|
|
related_name="%(app_label)s_%(class)s_items",
|
|
)
|
|
one = models.ForeignKey(
|
|
"ModelForReusableThroughModel", on_delete=models.CASCADE, null=True, blank=True
|
|
)
|
|
two = models.ForeignKey(
|
|
"OtherModelForReusableThroughModel",
|
|
on_delete=models.CASCADE,
|
|
null=True,
|
|
blank=True,
|
|
)
|
|
|
|
|
|
class ModelForReusableThroughModel(models.Model):
|
|
"""
|
|
A model with many-to-many relations through a shared model.
|
|
"""
|
|
|
|
name = models.CharField(max_length=200)
|
|
related = models.ManyToManyField(
|
|
ReusableThroughRelatedModel, through=ReusableThroughModel
|
|
)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
class OtherModelForReusableThroughModel(models.Model):
|
|
"""
|
|
Another model with many-to-many relations through a shared model.
|
|
"""
|
|
|
|
name = models.CharField(max_length=200)
|
|
related = models.ManyToManyField(
|
|
ReusableThroughRelatedModel, through=ReusableThroughModel
|
|
)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
@auditlog.register(include_fields=["label"])
|
|
class SimpleIncludeModel(models.Model):
|
|
"""
|
|
A simple model used for register's include_fields kwarg
|
|
"""
|
|
|
|
label = models.CharField(max_length=100)
|
|
text = models.TextField(blank=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
class SimpleExcludeModel(models.Model):
|
|
"""
|
|
A simple model used for register's exclude_fields kwarg
|
|
"""
|
|
|
|
label = models.CharField(max_length=100)
|
|
text = models.TextField(blank=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
class SimpleMappingModel(models.Model):
|
|
"""
|
|
A simple model used for register's mapping_fields kwarg
|
|
"""
|
|
|
|
sku = models.CharField(max_length=100)
|
|
vtxt = models.CharField(verbose_name="Version", max_length=100)
|
|
not_mapped = models.CharField(max_length=100)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
@auditlog.register(mask_fields=["address"])
|
|
class SimpleMaskedModel(models.Model):
|
|
"""
|
|
A simple model used for register's mask_fields kwarg
|
|
"""
|
|
|
|
address = models.CharField(max_length=100)
|
|
text = models.TextField()
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
class AdditionalDataIncludedModel(models.Model):
|
|
"""
|
|
A model where get_additional_data is defined which allows for logging extra
|
|
information about the model in JSON
|
|
"""
|
|
|
|
label = models.CharField(max_length=100)
|
|
text = models.TextField(blank=True)
|
|
related = models.ForeignKey(to=SimpleModel, on_delete=models.CASCADE)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
def get_additional_data(self):
|
|
"""
|
|
Returns JSON that captures a snapshot of additional details of the
|
|
model instance. This method, if defined, is accessed by auditlog
|
|
manager and added to each logentry instance on creation.
|
|
"""
|
|
object_details = {
|
|
"related_model_id": self.related.id,
|
|
"related_model_text": self.related.text,
|
|
}
|
|
return object_details
|
|
|
|
|
|
class DateTimeFieldModel(models.Model):
|
|
"""
|
|
A model with a DateTimeField, used to test DateTimeField
|
|
changes are detected properly.
|
|
"""
|
|
|
|
label = models.CharField(max_length=100)
|
|
timestamp = models.DateTimeField()
|
|
date = models.DateField()
|
|
time = models.TimeField()
|
|
naive_dt = models.DateTimeField(null=True, blank=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
class ChoicesFieldModel(models.Model):
|
|
"""
|
|
A model with a CharField restricted to a set of choices.
|
|
This model is used to test the changes_display_dict method.
|
|
"""
|
|
|
|
RED = "r"
|
|
YELLOW = "y"
|
|
GREEN = "g"
|
|
|
|
STATUS_CHOICES = (
|
|
(RED, "Red"),
|
|
(YELLOW, "Yellow"),
|
|
(GREEN, "Green"),
|
|
)
|
|
|
|
status = models.CharField(max_length=1, choices=STATUS_CHOICES)
|
|
multiplechoice = models.CharField(max_length=255, choices=STATUS_CHOICES)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
class CharfieldTextfieldModel(models.Model):
|
|
"""
|
|
A model with a max length CharField and a Textfield.
|
|
This model is used to test the changes_display_dict
|
|
method's ability to truncate long text.
|
|
"""
|
|
|
|
longchar = models.CharField(max_length=255)
|
|
longtextfield = models.TextField()
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
# Only define PostgreSQL-specific models when ArrayField is available
|
|
if settings.TEST_DB_BACKEND == "postgresql":
|
|
from django.contrib.postgres.fields import ArrayField
|
|
|
|
class PostgresArrayFieldModel(models.Model):
|
|
"""
|
|
Test auditlog with Postgres's ArrayField
|
|
"""
|
|
|
|
RED = "r"
|
|
YELLOW = "y"
|
|
GREEN = "g"
|
|
|
|
STATUS_CHOICES = (
|
|
(RED, "Red"),
|
|
(YELLOW, "Yellow"),
|
|
(GREEN, "Green"),
|
|
)
|
|
|
|
arrayfield = ArrayField(
|
|
models.CharField(max_length=1, choices=STATUS_CHOICES), size=3
|
|
)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
else:
|
|
|
|
class PostgresArrayFieldModel(models.Model):
|
|
class Meta:
|
|
managed = False
|
|
|
|
|
|
class NoDeleteHistoryModel(models.Model):
|
|
integer = models.IntegerField(blank=True, null=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=False)
|
|
|
|
|
|
class JSONModel(models.Model):
|
|
json = models.JSONField(default=dict, encoder=DjangoJSONEncoder)
|
|
|
|
history = AuditlogHistoryField(delete_related=False)
|
|
|
|
|
|
class NullableJSONModel(models.Model):
|
|
json = models.JSONField(null=True, blank=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=False)
|
|
|
|
|
|
class SerializeThisModel(models.Model):
|
|
label = models.CharField(max_length=24, unique=True)
|
|
timestamp = models.DateTimeField()
|
|
nullable = models.IntegerField(null=True)
|
|
nested = models.JSONField()
|
|
mask_me = models.CharField(max_length=255, null=True)
|
|
code = models.UUIDField(null=True)
|
|
date = models.DateField(null=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=False)
|
|
|
|
def natural_key(self):
|
|
return self.label
|
|
|
|
|
|
class SerializeOnlySomeOfThisModel(models.Model):
|
|
this = models.CharField(max_length=24)
|
|
not_this = models.CharField(max_length=24)
|
|
|
|
history = AuditlogHistoryField(delete_related=False)
|
|
|
|
|
|
class SerializePrimaryKeyRelatedModel(models.Model):
|
|
serialize_this = models.ForeignKey(to=SerializeThisModel, on_delete=models.CASCADE)
|
|
subheading = models.CharField(max_length=255)
|
|
value = models.IntegerField()
|
|
|
|
history = AuditlogHistoryField(delete_related=False)
|
|
|
|
|
|
class SerializeNaturalKeyRelatedModel(models.Model):
|
|
serialize_this = models.ForeignKey(to=SerializeThisModel, on_delete=models.CASCADE)
|
|
subheading = models.CharField(max_length=255)
|
|
value = models.IntegerField()
|
|
|
|
history = AuditlogHistoryField(delete_related=False)
|
|
|
|
|
|
class SimpleNonManagedModel(models.Model):
|
|
"""
|
|
A simple model with no special things going on.
|
|
"""
|
|
|
|
text = models.TextField(blank=True)
|
|
boolean = models.BooleanField(default=False)
|
|
integer = models.IntegerField(blank=True, null=True)
|
|
datetime = models.DateTimeField(auto_now=True)
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
def __str__(self):
|
|
return self.text
|
|
|
|
class Meta:
|
|
managed = False
|
|
|
|
|
|
class SecretManager(models.Manager):
|
|
def get_queryset(self):
|
|
return super().get_queryset().filter(is_secret=False)
|
|
|
|
|
|
@auditlog.register()
|
|
class SwappedManagerModel(models.Model):
|
|
is_secret = models.BooleanField(default=False)
|
|
name = models.CharField(max_length=255)
|
|
|
|
objects = SecretManager()
|
|
|
|
|
|
class AutoManyRelatedModel(models.Model):
|
|
related = models.ManyToManyField(SimpleModel)
|
|
|
|
|
|
class CustomMaskModel(models.Model):
|
|
credit_card = models.CharField(max_length=16)
|
|
text = models.TextField()
|
|
|
|
history = AuditlogHistoryField(delete_related=True)
|
|
|
|
|
|
auditlog.register(AltPrimaryKeyModel)
|
|
auditlog.register(UUIDPrimaryKeyModel)
|
|
auditlog.register(ModelPrimaryKeyModel)
|
|
auditlog.register(ProxyModel)
|
|
auditlog.register(RelatedModel)
|
|
auditlog.register(ManyRelatedModel)
|
|
auditlog.register(ManyRelatedModel.recursive.through)
|
|
m2m_only_auditlog.register(ManyRelatedModel, m2m_fields={"related"})
|
|
m2m_only_auditlog.register(ModelForReusableThroughModel, m2m_fields={"related"})
|
|
m2m_only_auditlog.register(OtherModelForReusableThroughModel, m2m_fields={"related"})
|
|
auditlog.register(SimpleExcludeModel, exclude_fields=["text"])
|
|
auditlog.register(SimpleMappingModel, mapping_fields={"sku": "Product No."})
|
|
auditlog.register(AdditionalDataIncludedModel)
|
|
auditlog.register(DateTimeFieldModel)
|
|
auditlog.register(ChoicesFieldModel)
|
|
auditlog.register(CharfieldTextfieldModel)
|
|
if settings.TEST_DB_BACKEND == "postgresql":
|
|
auditlog.register(PostgresArrayFieldModel)
|
|
auditlog.register(NoDeleteHistoryModel)
|
|
auditlog.register(JSONModel)
|
|
auditlog.register(NullableJSONModel)
|
|
auditlog.register(
|
|
SerializeThisModel,
|
|
serialize_data=True,
|
|
mask_fields=["mask_me"],
|
|
)
|
|
auditlog.register(
|
|
SerializeOnlySomeOfThisModel,
|
|
serialize_data=True,
|
|
serialize_auditlog_fields_only=True,
|
|
exclude_fields=["not_this"],
|
|
)
|
|
auditlog.register(SerializePrimaryKeyRelatedModel, serialize_data=True)
|
|
auditlog.register(
|
|
SerializeNaturalKeyRelatedModel,
|
|
serialize_data=True,
|
|
serialize_kwargs={"use_natural_foreign_keys": True},
|
|
)
|
|
auditlog.register(
|
|
CustomMaskModel,
|
|
mask_fields=["credit_card"],
|
|
mask_callable="auditlog_tests.test_app.mask.custom_mask_str",
|
|
)
|