mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'logging' into 'master'
Logs See merge request bde/nk20!28
This commit is contained in:
commit
5b995f16f1
|
@ -0,0 +1,4 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'api.apps.APIConfig'
|
|
@ -0,0 +1,10 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class APIConfig(AppConfig):
|
||||
name = 'api'
|
||||
verbose_name = _('API')
|
|
@ -0,0 +1,4 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'logs.apps.LogsConfig'
|
|
@ -0,0 +1,14 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class LogsConfig(AppConfig):
|
||||
name = 'logs'
|
||||
verbose_name = _('Logs')
|
||||
|
||||
def ready(self):
|
||||
# noinspection PyUnresolvedReferences
|
||||
import logs.signals
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Changelog(models.Model):
|
||||
"""
|
||||
Store each modification on the database (except sessions and logging),
|
||||
including creating, editing and deleting models.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.PROTECT,
|
||||
null=True,
|
||||
verbose_name=_('user'),
|
||||
)
|
||||
|
||||
ip = models.GenericIPAddressField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_("IP Address")
|
||||
)
|
||||
|
||||
model = models.ForeignKey(
|
||||
ContentType,
|
||||
on_delete=models.PROTECT,
|
||||
null=False,
|
||||
blank=False,
|
||||
verbose_name=_('model'),
|
||||
)
|
||||
|
||||
instance_pk = models.CharField(
|
||||
max_length=255,
|
||||
null=False,
|
||||
blank=False,
|
||||
verbose_name=_('identifier'),
|
||||
)
|
||||
|
||||
previous = models.TextField(
|
||||
null=True,
|
||||
verbose_name=_('previous data'),
|
||||
)
|
||||
|
||||
data = models.TextField(
|
||||
null=True,
|
||||
verbose_name=_('new data'),
|
||||
)
|
||||
|
||||
action = models.CharField( # create, edit or delete
|
||||
max_length=16,
|
||||
null=False,
|
||||
blank=False,
|
||||
verbose_name=_('action'),
|
||||
)
|
||||
|
||||
timestamp = models.DateTimeField(
|
||||
null=False,
|
||||
blank=False,
|
||||
auto_now_add=True,
|
||||
name='timestamp',
|
||||
verbose_name=_('timestamp'),
|
||||
)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
raise ValidationError(_("Logs cannot be destroyed."))
|
|
@ -0,0 +1,119 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import inspect
|
||||
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core import serializers
|
||||
from django.db.models.signals import pre_save, post_save, post_delete
|
||||
from django.dispatch import receiver
|
||||
from .models import Changelog
|
||||
|
||||
|
||||
def get_request_in_signal(sender):
|
||||
req = None
|
||||
for entry in reversed(inspect.stack()):
|
||||
try:
|
||||
req = entry[0].f_locals['request']
|
||||
# Check if there is a user
|
||||
# noinspection PyStatementEffect
|
||||
req.user
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
if not req:
|
||||
print("WARNING: Attempt to save " + str(sender) + " with no user")
|
||||
|
||||
return req
|
||||
|
||||
|
||||
def get_user_and_ip(sender):
|
||||
req = get_request_in_signal(sender)
|
||||
try:
|
||||
user = req.user
|
||||
if 'HTTP_X_FORWARDED_FOR' in req.META:
|
||||
ip = req.META.get('HTTP_X_FORWARDED_FOR')
|
||||
else:
|
||||
ip = req.META.get('REMOTE_ADDR')
|
||||
except:
|
||||
user = None
|
||||
ip = None
|
||||
return user, ip
|
||||
|
||||
|
||||
EXCLUDED = [
|
||||
'admin.logentry',
|
||||
'authtoken.token',
|
||||
'cas_server.user',
|
||||
'cas_server.userattributes',
|
||||
'contenttypes.contenttype',
|
||||
'logs.changelog',
|
||||
'migrations.migration',
|
||||
'note.noteuser',
|
||||
'note.noteclub',
|
||||
'note.notespecial',
|
||||
'sessions.session',
|
||||
'reversion.revision',
|
||||
'reversion.version',
|
||||
]
|
||||
|
||||
|
||||
@receiver(pre_save)
|
||||
def pre_save_object(sender, instance, **kwargs):
|
||||
qs = sender.objects.filter(pk=instance.pk).all()
|
||||
if qs.exists():
|
||||
instance._previous = qs.get()
|
||||
else:
|
||||
instance._previous = None
|
||||
|
||||
|
||||
@receiver(post_save)
|
||||
def save_object(sender, instance, **kwargs):
|
||||
# noinspection PyProtectedMember
|
||||
if instance._meta.label_lower in EXCLUDED:
|
||||
return
|
||||
|
||||
previous = instance._previous
|
||||
|
||||
user, ip = get_user_and_ip(sender)
|
||||
|
||||
if user is not None and instance._meta.label_lower == "auth.user" and previous:
|
||||
# Don't save last login modifications
|
||||
if instance.last_login != previous.last_login:
|
||||
return
|
||||
|
||||
previous_json = serializers.serialize('json', [previous, ])[1:-1] if previous else None
|
||||
instance_json = serializers.serialize('json', [instance, ])[1:-1]
|
||||
|
||||
if previous_json == instance_json:
|
||||
# No modification
|
||||
return
|
||||
|
||||
Changelog.objects.create(user=user,
|
||||
ip=ip,
|
||||
model=ContentType.objects.get_for_model(instance),
|
||||
instance_pk=instance.pk,
|
||||
previous=previous_json,
|
||||
data=instance_json,
|
||||
action=("edit" if previous else "create")
|
||||
).save()
|
||||
|
||||
|
||||
@receiver(post_delete)
|
||||
def delete_object(sender, instance, **kwargs):
|
||||
# noinspection PyProtectedMember
|
||||
if instance._meta.label_lower in EXCLUDED:
|
||||
return
|
||||
|
||||
user, ip = get_user_and_ip(sender)
|
||||
|
||||
instance_json = serializers.serialize('json', [instance, ])[1:-1]
|
||||
Changelog.objects.create(user=user,
|
||||
ip=ip,
|
||||
model=ContentType.objects.get_for_model(instance),
|
||||
instance_pk=instance.pk,
|
||||
previous=instance_json,
|
||||
data=None,
|
||||
action="delete"
|
||||
).save()
|
|
@ -0,0 +1,8 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
app_name = 'logs'
|
||||
|
||||
# TODO User interface
|
||||
urlpatterns = [
|
||||
]
|
|
@ -2,9 +2,22 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import post_save
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .signals import save_user_profile
|
||||
|
||||
|
||||
class MemberConfig(AppConfig):
|
||||
name = 'member'
|
||||
verbose_name = _('member')
|
||||
|
||||
def ready(self):
|
||||
"""
|
||||
Define app internal signals to interact with other apps
|
||||
"""
|
||||
post_save.connect(
|
||||
save_user_profile,
|
||||
sender=settings.AUTH_USER_MODEL,
|
||||
)
|
||||
|
|
|
@ -1,2 +1,15 @@
|
|||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
def save_user_profile(instance, created, raw, **_kwargs):
|
||||
"""
|
||||
Hook to create and save a profile when an user is updated if it is not registered with the signup form
|
||||
"""
|
||||
if raw:
|
||||
# When provisionning data, do not try to autocreate
|
||||
return
|
||||
|
||||
if created:
|
||||
from .models import Profile
|
||||
Profile.objects.get_or_create(user=instance)
|
||||
instance.profile.save()
|
||||
|
|
|
@ -82,6 +82,46 @@ msgstr ""
|
|||
msgid "guests"
|
||||
msgstr ""
|
||||
|
||||
#: apps/api/apps.py:10
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/apps.py:10
|
||||
msgid "Logs"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:20 apps/note/models/notes.py:105
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:27
|
||||
msgid "model"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:34
|
||||
msgid "identifier"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:39
|
||||
msgid "previous data"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:44
|
||||
msgid "new data"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:51
|
||||
msgid "action"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:59
|
||||
msgid "timestamp"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:63
|
||||
msgid "Logs cannot be destroyed."
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/apps.py:10
|
||||
msgid "member"
|
||||
msgstr ""
|
||||
|
|
|
@ -77,6 +77,50 @@ msgstr "invité"
|
|||
msgid "guests"
|
||||
msgstr "invités"
|
||||
|
||||
#: apps/api/apps.py:10
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/apps.py:10
|
||||
msgid "Logs"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:20 apps/note/models/notes.py:105
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
|
||||
#: apps/logs/models.py:27
|
||||
msgid "model"
|
||||
msgstr "Modèle"
|
||||
|
||||
#: apps/logs/models.py:34
|
||||
msgid "identifier"
|
||||
msgstr "Identifiant"
|
||||
|
||||
#: apps/logs/models.py:39
|
||||
msgid "previous data"
|
||||
msgstr "Données précédentes"
|
||||
|
||||
#: apps/logs/models.py:44
|
||||
#, fuzzy
|
||||
#| msgid "end date"
|
||||
msgid "new data"
|
||||
msgstr "Nouvelles données"
|
||||
|
||||
#: apps/logs/models.py:51
|
||||
#, fuzzy
|
||||
#| msgid "section"
|
||||
msgid "action"
|
||||
msgstr "Action"
|
||||
|
||||
#: apps/logs/models.py:59
|
||||
msgid "timestamp"
|
||||
msgstr "Date"
|
||||
|
||||
#: apps/logs/models.py:63
|
||||
msgid "Logs cannot be destroyed."
|
||||
msgstr "Les logs ne peuvent pas être détruits."
|
||||
|
||||
#: apps/member/apps.py:10
|
||||
msgid "member"
|
||||
msgstr "adhérent"
|
||||
|
|
|
@ -64,6 +64,7 @@ INSTALLED_APPS = [
|
|||
'member',
|
||||
'note',
|
||||
'api',
|
||||
'logs',
|
||||
]
|
||||
LOGIN_REDIRECT_URL = '/note/transfer/'
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ urlpatterns = [
|
|||
|
||||
# Include Django REST API
|
||||
path('api/', include('api.urls')),
|
||||
|
||||
path('logs/', include('logs.urls')),
|
||||
]
|
||||
|
||||
urlpatterns += static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)
|
||||
|
|
Loading…
Reference in New Issue