kopia lustrzana https://github.com/wagtail/wagtail
Move logging models to wagtail.core.models.audit_log
rodzic
99ae907c97
commit
c04fe47dd0
|
@ -9,7 +9,6 @@ from urllib.parse import urlparse
|
|||
from django import forms
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import checks
|
||||
|
@ -52,6 +51,7 @@ from wagtail.core.utils import (
|
|||
get_supported_content_language_variant, resolve_model_string)
|
||||
from wagtail.search import index
|
||||
|
||||
from .audit_log import BaseLogEntry, BaseLogEntryManager, LogEntryQuerySet # noqa
|
||||
from .collections import ( # noqa
|
||||
BaseCollectionManager, Collection, CollectionManager, CollectionMember,
|
||||
CollectionViewRestriction, GroupCollectionPermission, GroupCollectionPermissionManager,
|
||||
|
@ -4316,65 +4316,6 @@ class TaskState(models.Model):
|
|||
verbose_name_plural = _('Task states')
|
||||
|
||||
|
||||
class LogEntryQuerySet(models.QuerySet):
|
||||
def get_users(self):
|
||||
"""
|
||||
Returns a QuerySet of Users who have created at least one log entry in this QuerySet.
|
||||
|
||||
The returned queryset is ordered by the username.
|
||||
"""
|
||||
User = get_user_model()
|
||||
return User.objects.filter(
|
||||
pk__in=set(self.values_list('user__pk', flat=True))
|
||||
).order_by(User.USERNAME_FIELD)
|
||||
|
||||
|
||||
class BaseLogEntryManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return LogEntryQuerySet(self.model, using=self._db)
|
||||
|
||||
def get_instance_title(self, instance):
|
||||
return str(instance)
|
||||
|
||||
def log_action(self, instance, action, **kwargs):
|
||||
"""
|
||||
:param instance: The model instance we are logging an action for
|
||||
:param action: The action. Should be namespaced to app (e.g. wagtail.create, wagtail.workflow.start)
|
||||
:param kwargs: Addition fields to for the model deriving from BaseLogEntry
|
||||
- user: The user performing the action
|
||||
- title: the instance title
|
||||
- data: any additional metadata
|
||||
- content_changed, deleted - Boolean flags
|
||||
:return: The new log entry
|
||||
"""
|
||||
data = kwargs.pop('data', '')
|
||||
title = kwargs.pop('title', None)
|
||||
if not title:
|
||||
title = self.get_instance_title(instance)
|
||||
|
||||
timestamp = kwargs.pop('timestamp', timezone.now())
|
||||
return self.model.objects.create(
|
||||
content_type=ContentType.objects.get_for_model(instance, for_concrete_model=False),
|
||||
label=title,
|
||||
action=action,
|
||||
timestamp=timestamp,
|
||||
data_json=json.dumps(data),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def get_for_model(self, model):
|
||||
# Return empty queryset if the given object is not valid.
|
||||
if not issubclass(model, models.Model):
|
||||
return self.none()
|
||||
|
||||
ct = ContentType.objects.get_for_model(model)
|
||||
|
||||
return self.filter(content_type=ct)
|
||||
|
||||
def get_for_user(self, user_id):
|
||||
return self.filter(user=user_id)
|
||||
|
||||
|
||||
class PageLogEntryManager(BaseLogEntryManager):
|
||||
|
||||
def get_instance_title(self, instance):
|
||||
|
@ -4385,113 +4326,6 @@ class PageLogEntryManager(BaseLogEntryManager):
|
|||
return super().log_action(instance, action, **kwargs)
|
||||
|
||||
|
||||
class BaseLogEntry(models.Model):
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
models.SET_NULL,
|
||||
verbose_name=_('content type'),
|
||||
blank=True, null=True,
|
||||
related_name='+',
|
||||
)
|
||||
label = models.TextField()
|
||||
|
||||
action = models.CharField(max_length=255, blank=True, db_index=True)
|
||||
data_json = models.TextField(blank=True)
|
||||
timestamp = models.DateTimeField(verbose_name=_('timestamp (UTC)'))
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True, # Null if actioned by system
|
||||
blank=True,
|
||||
on_delete=models.DO_NOTHING,
|
||||
db_constraint=False,
|
||||
related_name='+',
|
||||
)
|
||||
|
||||
# Flags for additional context to the 'action' made by the user (or system).
|
||||
content_changed = models.BooleanField(default=False, db_index=True)
|
||||
deleted = models.BooleanField(default=False)
|
||||
|
||||
objects = BaseLogEntryManager()
|
||||
|
||||
action_registry = None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = _('log entry')
|
||||
verbose_name_plural = _('log entries')
|
||||
ordering = ['-timestamp']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
self.action_registry.scan_for_actions()
|
||||
|
||||
if self.action not in self.action_registry.actions:
|
||||
raise ValidationError({'action': _("The log action '{}' has not been registered.").format(self.action)})
|
||||
|
||||
def __str__(self):
|
||||
return "LogEntry %d: '%s' on '%s'" % (
|
||||
self.pk, self.action, self.object_verbose_name()
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def user_display_name(self):
|
||||
"""
|
||||
Returns the display name of the associated user;
|
||||
get_full_name if available and non-empty, otherwise get_username.
|
||||
Defaults to 'system' when none is provided
|
||||
"""
|
||||
if self.user_id:
|
||||
try:
|
||||
user = self.user
|
||||
except self._meta.get_field('user').related_model.DoesNotExist:
|
||||
# User has been deleted
|
||||
return _('user %(id)d (deleted)') % {'id': self.user_id}
|
||||
|
||||
try:
|
||||
full_name = user.get_full_name().strip()
|
||||
except AttributeError:
|
||||
full_name = ''
|
||||
return full_name or user.get_username()
|
||||
|
||||
else:
|
||||
return _('system')
|
||||
|
||||
@cached_property
|
||||
def data(self):
|
||||
"""
|
||||
Provides deserialized data
|
||||
"""
|
||||
if self.data_json:
|
||||
return json.loads(self.data_json)
|
||||
else:
|
||||
return {}
|
||||
|
||||
@cached_property
|
||||
def object_verbose_name(self):
|
||||
model_class = self.content_type.model_class()
|
||||
if model_class is None:
|
||||
return self.content_type_id
|
||||
|
||||
return model_class._meta.verbose_name.title
|
||||
|
||||
def object_id(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def format_message(self):
|
||||
return self.action_registry.format_message(self)
|
||||
|
||||
def format_comment(self):
|
||||
return self.action_registry.format_comment(self)
|
||||
|
||||
@property
|
||||
def comment(self):
|
||||
return self.format_comment()
|
||||
|
||||
|
||||
class PageLogEntry(BaseLogEntry):
|
||||
page = models.ForeignKey(
|
||||
'wagtailcore.Page',
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class LogEntryQuerySet(models.QuerySet):
|
||||
def get_users(self):
|
||||
"""
|
||||
Returns a QuerySet of Users who have created at least one log entry in this QuerySet.
|
||||
|
||||
The returned queryset is ordered by the username.
|
||||
"""
|
||||
User = get_user_model()
|
||||
return User.objects.filter(
|
||||
pk__in=set(self.values_list('user__pk', flat=True))
|
||||
).order_by(User.USERNAME_FIELD)
|
||||
|
||||
|
||||
class BaseLogEntryManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return LogEntryQuerySet(self.model, using=self._db)
|
||||
|
||||
def get_instance_title(self, instance):
|
||||
return str(instance)
|
||||
|
||||
def log_action(self, instance, action, **kwargs):
|
||||
"""
|
||||
:param instance: The model instance we are logging an action for
|
||||
:param action: The action. Should be namespaced to app (e.g. wagtail.create, wagtail.workflow.start)
|
||||
:param kwargs: Addition fields to for the model deriving from BaseLogEntry
|
||||
- user: The user performing the action
|
||||
- title: the instance title
|
||||
- data: any additional metadata
|
||||
- content_changed, deleted - Boolean flags
|
||||
:return: The new log entry
|
||||
"""
|
||||
data = kwargs.pop('data', '')
|
||||
title = kwargs.pop('title', None)
|
||||
if not title:
|
||||
title = self.get_instance_title(instance)
|
||||
|
||||
timestamp = kwargs.pop('timestamp', timezone.now())
|
||||
return self.model.objects.create(
|
||||
content_type=ContentType.objects.get_for_model(instance, for_concrete_model=False),
|
||||
label=title,
|
||||
action=action,
|
||||
timestamp=timestamp,
|
||||
data_json=json.dumps(data),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def get_for_model(self, model):
|
||||
# Return empty queryset if the given object is not valid.
|
||||
if not issubclass(model, models.Model):
|
||||
return self.none()
|
||||
|
||||
ct = ContentType.objects.get_for_model(model)
|
||||
|
||||
return self.filter(content_type=ct)
|
||||
|
||||
def get_for_user(self, user_id):
|
||||
return self.filter(user=user_id)
|
||||
|
||||
|
||||
class BaseLogEntry(models.Model):
|
||||
content_type = models.ForeignKey(
|
||||
ContentType,
|
||||
models.SET_NULL,
|
||||
verbose_name=_('content type'),
|
||||
blank=True, null=True,
|
||||
related_name='+',
|
||||
)
|
||||
label = models.TextField()
|
||||
|
||||
action = models.CharField(max_length=255, blank=True, db_index=True)
|
||||
data_json = models.TextField(blank=True)
|
||||
timestamp = models.DateTimeField(verbose_name=_('timestamp (UTC)'))
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
null=True, # Null if actioned by system
|
||||
blank=True,
|
||||
on_delete=models.DO_NOTHING,
|
||||
db_constraint=False,
|
||||
related_name='+',
|
||||
)
|
||||
|
||||
# Flags for additional context to the 'action' made by the user (or system).
|
||||
content_changed = models.BooleanField(default=False, db_index=True)
|
||||
deleted = models.BooleanField(default=False)
|
||||
|
||||
objects = BaseLogEntryManager()
|
||||
|
||||
action_registry = None
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
verbose_name = _('log entry')
|
||||
verbose_name_plural = _('log entries')
|
||||
ordering = ['-timestamp']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.full_clean()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
self.action_registry.scan_for_actions()
|
||||
|
||||
if self.action not in self.action_registry.actions:
|
||||
raise ValidationError({'action': _("The log action '{}' has not been registered.").format(self.action)})
|
||||
|
||||
def __str__(self):
|
||||
return "LogEntry %d: '%s' on '%s'" % (
|
||||
self.pk, self.action, self.object_verbose_name()
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def user_display_name(self):
|
||||
"""
|
||||
Returns the display name of the associated user;
|
||||
get_full_name if available and non-empty, otherwise get_username.
|
||||
Defaults to 'system' when none is provided
|
||||
"""
|
||||
if self.user_id:
|
||||
try:
|
||||
user = self.user
|
||||
except self._meta.get_field('user').related_model.DoesNotExist:
|
||||
# User has been deleted
|
||||
return _('user %(id)d (deleted)') % {'id': self.user_id}
|
||||
|
||||
try:
|
||||
full_name = user.get_full_name().strip()
|
||||
except AttributeError:
|
||||
full_name = ''
|
||||
return full_name or user.get_username()
|
||||
|
||||
else:
|
||||
return _('system')
|
||||
|
||||
@cached_property
|
||||
def data(self):
|
||||
"""
|
||||
Provides deserialized data
|
||||
"""
|
||||
if self.data_json:
|
||||
return json.loads(self.data_json)
|
||||
else:
|
||||
return {}
|
||||
|
||||
@cached_property
|
||||
def object_verbose_name(self):
|
||||
model_class = self.content_type.model_class()
|
||||
if model_class is None:
|
||||
return self.content_type_id
|
||||
|
||||
return model_class._meta.verbose_name.title
|
||||
|
||||
def object_id(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def format_message(self):
|
||||
return self.action_registry.format_message(self)
|
||||
|
||||
def format_comment(self):
|
||||
return self.action_registry.format_comment(self)
|
||||
|
||||
@property
|
||||
def comment(self):
|
||||
return self.format_comment()
|
Ładowanie…
Reference in New Issue