WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Support for CustomLogEntry Model #761

@mostafaeftekharizadeh

Description

@mostafaeftekharizadeh

Hello,
Thank you for providing this useful module.

Currently, we can use additional_data to store extra information when creating a log. However, sometimes it is necessary to filter logs based on this extra information. Since additional_data is stored as JSON, if the number of log records grows significantly, querying on this field becomes expensive and slows down database performance.

For example, our users belong to multiple groups that represent their roles. We need to store a user’s role at the time of logging so that logs can later be filtered by role.

Proposal:
Introduce support for replacing the default LogEntry model with a custom one. For instance, add a configuration option like AUDITLOG_CUSTOM_LOG_ENTRY_MODEL. If this setting is defined, the system should use the custom model for storing logs.

Modification in conf.py to add AUDITLOG_CUSTOM_LOG_ENTRY_MODEL configuration

settings.AUDITLOG_CUSTOM_LOG_ENTRY_MODEL = getattr(
    settings, "AUDITLOG_CUSTOM_LOG_ENTRY_MODEL", None
)

Additionally, modifications in middleware.py can allow passing extra information during a request, for example:

def __call__(self, request):
    user = self._get_actor(request)

    set_cid(request)

    context_data = {
        "role": request.user.get_active_role(),
        "remote_addr": self._get_remote_addr(request),
        "remote_port": self._get_remote_port(request),
    }

    with set_extra_data(actor=user, context_data=context_data):
        return self.get_response(request)

Also, changes in context.py would allow persisting additional information:

@contextlib.contextmanager
def set_extra_data(actor, context_data={}):
    LogEntry = get_log_entry_model()
    # Initialize thread local storage
    context_data["signal_duid"] = ("set_extra_data", time.time())
    auditlog_value.set(context_data)

    # Connect signal for automatic logging
    set_actor = partial(
        _set_extra_data,
        user=actor,
        signal_duid=context_data["signal_duid"],
    )
    ...

def _set_actor(user, sender, instance):
    LogEntry = get_log_entry_model()
    auth_user_model = get_user_model()
    if (
        sender == LogEntry
        and isinstance(user, auth_user_model)
        and instance.actor is None
    ):
        instance.actor = user
        instance.actor_email = getattr(user, "email", None)


def _set_extra_data(user, sender, instance, signal_duid, **kwargs):
    try:
        auditlog = auditlog_value.get()
    except LookupError:
        return

    if signal_duid != auditlog["signal_duid"]:
        return

    _set_actor(user, sender, instance)

    for key in auditlog:
        if hasattr(instance, key):
            setattr(instance, key, auditlog[key])

Define Custom LogEntry model in the main project models

class CustomLogEntry(LogEntry):
    role = models.ForeignKey(Group, blank=True, null=True, on_delete=models.CASCADE, related_name="logs")
    objects = LogEntryManager()

Set AUDITLOG_CUSTOM_LOG_ENTRY_MODEL in main project settings

AUDITLOG_CUSTOM_LOG_ENTRY_MODEL = "app.CustomLogEntry"

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions