Commit 50c2f816 authored by Sli's avatar Sli
Browse files

Merge branch 'deletion_logs' into 'master'

Add generic operation logs and implements it for Sellings and Refilling deletions

See merge request !259
parents 129f2e53 5c30de5f
Pipeline #2139 passed with stage
in 44 minutes and 32 seconds
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
# #
import importlib import importlib
import threading
from django.conf import settings from django.conf import settings
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.contrib.auth import get_user from django.contrib.auth import get_user
...@@ -49,8 +50,31 @@ class AuthenticationMiddleware(DjangoAuthenticationMiddleware): ...@@ -49,8 +50,31 @@ class AuthenticationMiddleware(DjangoAuthenticationMiddleware):
def process_request(self, request): def process_request(self, request):
assert hasattr(request, "session"), ( assert hasattr(request, "session"), (
"The Django authentication middleware requires session middleware " "The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE_CLASSES setting to insert " "to be installed. Edit your MIDDLEWARE setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before " "'django.contrib.sessions.middleware.SessionMiddleware' before "
"'account.middleware.AuthenticationMiddleware'." "'account.middleware.AuthenticationMiddleware'."
) )
request.user = SimpleLazyObject(lambda: get_cached_user(request)) request.user = SimpleLazyObject(lambda: get_cached_user(request))
_threadlocal = threading.local()
def get_signal_request():
"""
!!! Do not use if your operation is asynchronus !!!
Allow to access current request in signals
This is a hack that looks into the thread
Mainly used for log purpose
"""
return getattr(_threadlocal, "request", None)
class SignalRequestMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
setattr(_threadlocal, "request", request)
return self.get_response(request)
# Generated by Django 2.2.6 on 2019-11-14 15:10
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("core", "0033_auto_20191006_0049"),
]
operations = [
migrations.CreateModel(
name="OperationLog",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("date", models.DateTimeField(auto_now_add=True, verbose_name="date")),
("label", models.CharField(max_length=255, verbose_name="label")),
(
"operation_type",
models.CharField(
choices=[
("SELLING_DELETION", "Selling deletion"),
("REFILLING_DELETION", "Refilling deletion"),
],
max_length=40,
verbose_name="operation type",
),
),
(
"operator",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="logs",
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
...@@ -1454,3 +1454,24 @@ class Gift(models.Model): ...@@ -1454,3 +1454,24 @@ class Gift(models.Model):
def is_owned_by(self, user): def is_owned_by(self, user):
return user.is_board_member or user.is_root return user.is_board_member or user.is_root
class OperationLog(models.Model):
"""
General purpose log object to register operations
"""
date = models.DateTimeField(_("date"), auto_now_add=True)
label = models.CharField(_("label"), max_length=255)
operator = models.ForeignKey(
User, related_name="logs", on_delete=models.SET_NULL, null=True
)
operation_type = models.CharField(
_("operation type"), max_length=40, choices=settings.SITH_LOG_OPERATION_TYPE,
)
def is_owned_by(self, user):
return user.is_root
def __str__(self):
return "%s - %s - %s" % (self.operation_type, self.label, self.operator)
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
<td>{{ i.amount }}</td> <td>{{ i.amount }}</td>
<td>{{ i.get_payment_method_display() }}</td> <td>{{ i.get_payment_method_display() }}</td>
{% if i.is_owned_by(user) %} {% if i.is_owned_by(user) %}
<td><a href="{{ url('counter:refilling_delete', refilling_id=i.id) }}">Delete</a></td> <td><a href="{{ url('counter:refilling_delete', refilling_id=i.id) }}">{% trans %}Delete{% endtrans %}</a></td>
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {% endfor %}
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
{% if user.is_root %} {% if user.is_root %}
<li><a href="{{ url('core:group_list') }}">{% trans %}Groups{% endtrans %}</a></li> <li><a href="{{ url('core:group_list') }}">{% trans %}Groups{% endtrans %}</a></li>
<li><a href="{{ url('rootplace:merge') }}">{% trans %}Merge users{% endtrans %}</a></li> <li><a href="{{ url('rootplace:merge') }}">{% trans %}Merge users{% endtrans %}</a></li>
<li><a href="{{ url('rootplace:operation_logs') }}">{% trans %}Operation logs{% endtrans %}</a></li>
<li><a href="{{ url('rootplace:delete_forum_messages') }}">{% trans %}Delete user's forum messages{% endtrans %}</a></li> <li><a href="{{ url('rootplace:delete_forum_messages') }}">{% trans %}Delete user's forum messages{% endtrans %}</a></li>
{% endif %} {% endif %}
{% if user.can_create_subscription or user.is_root %} {% if user.can_create_subscription or user.is_root %}
......
# -*- coding:utf-8 -* # -*- coding:utf-8 -*
# #
# Copyright 2016,2017 # Copyright 2016,2017,2019
# - Skia <skia@libskia.so> # - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr>
# #
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr. # http://ae.utbm.fr.
...@@ -21,3 +22,5 @@ ...@@ -21,3 +22,5 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
default_app_config = "counter.app.CounterConfig"
# -*- coding:utf-8 -*
#
# Copyright 2019
# - Sli <antoine@bartuccio.fr>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class CounterConfig(AppConfig):
name = "counter"
verbose_name = _("counter")
def ready(self):
import counter.signals
# -*- coding:utf-8 -*
#
# Copyright 2019
# - Sli <antoine@bartuccio.fr>
#
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr.
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License a published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
# Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
from django.db.models.signals import pre_delete
from django.dispatch import receiver
from django.conf import settings
from core.middleware import get_signal_request
from core.models import OperationLog
from counter.models import Selling, Refilling, Counter
def write_log(instance, operation_type):
def get_user():
request = get_signal_request()
if not request:
return None
# Get a random barmen if deletion is from a counter
session = getattr(request, "session", {})
session_token = session.get("counter_token", None)
if session_token:
counter = Counter.objects.filter(token=session_token).first()
if counter and len(counter.get_barmen_list()) > 0:
return counter.get_random_barman()
# Get the current logged user if not from a counter
if request.user and not request.user.is_anonymous:
return request.user
# Return None by default
return None
log = OperationLog(
label=str(instance), operator=get_user(), operation_type=operation_type,
).save()
@receiver(pre_delete, sender=Refilling, dispatch_uid="write_log_refilling_deletion")
def write_log_refilling_deletion(sender, instance, **kwargs):
write_log(instance, "REFILLING_DELETION")
@receiver(pre_delete, sender=Selling, dispatch_uid="write_log_refilling_deletion")
def write_log_selling_deletion(sender, instance, **kwargs):
write_log(instance, "SELLING_DELETION")
This diff is collapsed.
{% extends "core/base.jinja" %}
{% from 'core/macros.jinja' import paginate %}
{% block title %}
{% trans %}Operation logs{% endtrans %}
{% endblock %}
{% block content %}
<table>
<thead>
<tr>
<th>{% trans %}Date{% endtrans %}</th>
<th>{% trans %}Operation type{% endtrans %}</th>
<th>{% trans %}Label{% endtrans %}</th>
<th>{% trans %}Operator{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for log in object_list %}
<tr>
<td>{{ log.date }} </td>
<td>{{ log.get_operation_type_display() }}</td>
<td>{{ log.label }}</td>
<td>{{ log.operator }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<br>
{{ paginate(page_obj, paginator) }}
{% endblock content %}
\ No newline at end of file
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# #
# Copyright 2016,2017 # Copyright 2016,2017
# - Skia <skia@libskia.so> # - Skia <skia@libskia.so>
# - Sli <antoine@bartuccio.fr>
# #
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, # Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
# http://ae.utbm.fr. # http://ae.utbm.fr.
...@@ -33,4 +34,5 @@ urlpatterns = [ ...@@ -33,4 +34,5 @@ urlpatterns = [
DeleteAllForumUserMessagesView.as_view(), DeleteAllForumUserMessagesView.as_view(),
name="delete_forum_messages", name="delete_forum_messages",
), ),
re_path(r"^logs$", OperationLogListView.as_view(), name="operation_logs"),
] ]
...@@ -25,13 +25,15 @@ ...@@ -25,13 +25,15 @@
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
from django.views.generic import ListView
from django.urls import reverse from django.urls import reverse
from django import forms from django import forms
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
from core.models import User from core.views import CanEditPropMixin
from core.models import User, OperationLog
from counter.models import Customer from counter.models import Customer
from forum.models import ForumMessageMeta from forum.models import ForumMessageMeta
...@@ -165,3 +167,14 @@ class DeleteAllForumUserMessagesView(FormView): ...@@ -165,3 +167,14 @@ class DeleteAllForumUserMessagesView(FormView):
def get_success_url(self): def get_success_url(self):
return reverse("core:user_profile", kwargs={"user_id": self.user.id}) return reverse("core:user_profile", kwargs={"user_id": self.user.id})
class OperationLogListView(ListView, CanEditPropMixin):
"""
List all logs
"""
model = OperationLog
template_name = "rootplace/logs.jinja"
ordering = ["-date"]
paginate_by = 100
...@@ -106,6 +106,7 @@ MIDDLEWARE = ( ...@@ -106,6 +106,7 @@ MIDDLEWARE = (
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"core.middleware.AuthenticationMiddleware", "core.middleware.AuthenticationMiddleware",
"core.middleware.SignalRequestMiddleware",
) )
ROOT_URLCONF = "sith.urls" ROOT_URLCONF = "sith.urls"
...@@ -443,6 +444,11 @@ SITH_PEDAGOGY_UV_RESULT_GRADE = [ ...@@ -443,6 +444,11 @@ SITH_PEDAGOGY_UV_RESULT_GRADE = [
("ABS", _("Abs")), ("ABS", _("Abs")),
] ]
SITH_LOG_OPERATION_TYPE = [
(("SELLING_DELETION"), _("Selling deletion")),
(("REFILLING_DELETION"), _("Refilling deletion")),
]
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide" SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
SITH_ECOCUP_CONS = 1152 SITH_ECOCUP_CONS = 1152
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment