Browse Source

Adiciona log para create, update, delete. (#3016)

pull/3018/head
Edward 5 years ago
committed by GitHub
parent
commit
137fd4ecf6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      sapl/base/admin.py
  2. 33
      sapl/base/migrations/0038_auditlog.py
  3. 40
      sapl/base/models.py
  4. 47
      sapl/base/receivers.py
  5. 4
      sapl/base/signals.py
  6. 37
      sapl/crud/base.py
  7. 1
      sapl/materia/forms.py
  8. 17
      sapl/materia/views.py
  9. 5
      sapl/protocoloadm/forms.py
  10. 15
      sapl/protocoloadm/views.py
  11. 1
      sapl/rules/map_rules.py

31
sapl/base/admin.py

@ -1,8 +1,9 @@
from django.contrib import admin from django.contrib import admin
from django.core.urlresolvers import reverse
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from reversion.models import Revision from reversion.models import Revision
from sapl.base.models import AuditLog
from sapl.utils import register_all_models_in_admin from sapl.utils import register_all_models_in_admin
register_all_models_in_admin(__name__) register_all_models_in_admin(__name__)
@ -20,5 +21,31 @@ class RevisionAdmin(admin.ModelAdmin):
self.message_user(request, _('You cannot change history.')) self.message_user(request, _('You cannot change history.'))
return redirect('admin:reversion_revision_changelist') return redirect('admin:reversion_revision_changelist')
admin.site.register(Revision, RevisionAdmin) admin.site.register(Revision, RevisionAdmin)
class AuditLogAdmin(admin.ModelAdmin):
pass
def has_add_permission(self, request):
return False
# def has_change_permission(self, request, obj=None):
# return False
#
def has_delete_permission(self, request, obj=None):
return False
def save_model(self, request, obj, form, change):
pass
def delete_model(self, request, obj):
pass
def save_related(self, request, form, formsets, change):
pass
# Na linha acima register_all_models_in_admin registrou AuditLog
admin.site.unregister(AuditLog)
admin.site.register(AuditLog, AuditLogAdmin)

33
sapl/base/migrations/0038_auditlog.py

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.20 on 2019-10-15 13:33
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0037_auto_20190527_0901'),
]
operations = [
migrations.CreateModel(
name='AuditLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('username', models.CharField(blank=True, db_index=True, max_length=100, verbose_name='username')),
('operation', models.CharField(db_index=True, max_length=1, verbose_name='operation')),
('timestamp', models.DateTimeField(db_index=True, verbose_name='timestamp')),
('object', models.CharField(blank=True, max_length=4096, verbose_name='object')),
('object_id', models.PositiveIntegerField(db_index=True, verbose_name='object_id')),
('model_name', models.CharField(db_index=True, max_length=100, verbose_name='model')),
('app_name', models.CharField(db_index=True, max_length=100, verbose_name='app')),
],
options={
'verbose_name': 'AuditLog',
'verbose_name_plural': 'AuditLogs',
'ordering': ('-id',),
},
),
]

40
sapl/base/models.py

@ -271,6 +271,46 @@ class Autor(models.Model):
return '?' return '?'
class AuditLog(models.Model):
operation = ('C', 'D', 'U')
MAX_DATA_LENGTH = 4096 # 4KB de texto
username = models.CharField(max_length=100,
verbose_name=_('username'),
blank=True,
db_index=True)
operation = models.CharField(max_length=1,
verbose_name=_('operation'),
db_index=True)
timestamp = models.DateTimeField(verbose_name=_('timestamp'),
db_index=True)
object = models.CharField(max_length=MAX_DATA_LENGTH,
blank=True,
verbose_name=_('object'))
object_id = models.PositiveIntegerField(verbose_name=_('object_id'),
db_index=True)
model_name = models.CharField(max_length=100, verbose_name=_('model'),
db_index=True)
app_name = models.CharField(max_length=100,
verbose_name=_('app'),
db_index=True)
class Meta:
verbose_name = _('AuditLog')
verbose_name_plural = _('AuditLogs')
ordering = ('-id',)
def __str__(self):
return "[%s] %s %s.%s %s" % (self.timestamp,
self.operation,
self.app_name,
self.model_name,
self.username,
)
def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True, def cria_models_tipo_autor(app_config=None, verbosity=2, interactive=True,
using=DEFAULT_DB_ALIAS, **kwargs): using=DEFAULT_DB_ALIAS, **kwargs):

47
sapl/base/receivers.py

@ -1,12 +1,17 @@
from django.db.models.signals import post_delete, post_save import logging
from django.core import serializers
from django.db.models.signals import post_delete
from django.dispatch import receiver from django.dispatch import receiver
from django.utils import timezone
from sapl.base.email_utils import do_envia_email_tramitacao
from sapl.base.models import AuditLog
from sapl.base.signals import tramitacao_signal, post_delete_signal, post_save_signal
from sapl.materia.models import Tramitacao from sapl.materia.models import Tramitacao
from sapl.protocoloadm.models import TramitacaoAdministrativo from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.base.signals import tramitacao_signal
from sapl.utils import get_base_url from sapl.utils import get_base_url
from sapl.base.email_utils import do_envia_email_tramitacao
@receiver(tramitacao_signal) @receiver(tramitacao_signal)
def handle_tramitacao_signal(sender, **kwargs): def handle_tramitacao_signal(sender, **kwargs):
@ -39,3 +44,37 @@ def status_tramitacao_materia(sender, instance, **kwargs):
documento = instance.documento documento = instance.documento
documento.tramitacao = True documento.tramitacao = True
documento.save() documento.save()
@receiver(post_delete_signal)
@receiver(post_save_signal)
def audit_log(sender, **kwargs):
logger = logging.getLogger(__name__)
instance = kwargs.get('instance')
operation = kwargs.get('operation')
user = kwargs.get('request').user
model_name = instance.__class__.__name__
app_name = instance._meta.app_label
object_id = instance.id
data = serializers.serialize('json', [instance])
if len(data) > AuditLog.MAX_DATA_LENGTH:
data = data[:AuditLog.MAX_DATA_LENGTH]
if user:
username = user.username
else:
username = ''
try:
AuditLog.objects.create(username=username,
operation=operation,
model_name=model_name,
app_name=app_name,
timestamp=timezone.now(),
object_id=object_id,
object=data)
except Exception as e:
logger.error('Error saving auditing log object')
logger.error(e)

4
sapl/base/signals.py

@ -1,3 +1,7 @@
import django.dispatch import django.dispatch
tramitacao_signal = django.dispatch.Signal(providing_args=['post', 'request']) tramitacao_signal = django.dispatch.Signal(providing_args=['post', 'request'])
post_delete_signal = django.dispatch.Signal(providing_args=['instance', 'request'])
post_save_signal = django.dispatch.Signal(providing_args=['instance', 'operation', 'request'])

37
sapl/crud/base.py

@ -16,7 +16,6 @@ from django.http.response import Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.decorators import classonlymethod from django.utils.decorators import classonlymethod
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.utils.translation import string_concat from django.utils.translation import string_concat
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.views.generic import (CreateView, DeleteView, DetailView, ListView, from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
@ -24,6 +23,7 @@ from django.views.generic import (CreateView, DeleteView, DetailView, ListView,
from django.views.generic.base import ContextMixin from django.views.generic.base import ContextMixin
from django.views.generic.list import MultipleObjectMixin from django.views.generic.list import MultipleObjectMixin
from sapl.base.signals import post_delete_signal, post_save_signal
from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display from sapl.crispy_layout_mixin import CrispyLayoutFormMixin, get_field_display
from sapl.crispy_layout_mixin import SaplFormHelper from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL, from sapl.rules.map_rules import (RP_ADD, RP_CHANGE, RP_DELETE, RP_DETAIL,
@ -618,8 +618,37 @@ class CrudListView(PermissionRequiredContainerCrudMixin, ListView):
return queryset return queryset
class AuditLogMixin(object):
def delete(self, request, *args, **kwargs):
# Classe deve implementar um get_object(), i.e., deve ser uma View
deleted_object = self.get_object()
try:
return super(AuditLogMixin, self).delete(request, args, kwargs)
finally:
post_delete_signal.send(sender=None,
instance=deleted_object,
operation='D',
request=self.request)
# SAVE/UPDATE method
def form_valid(self, form):
try:
if not form.instance.pk:
operation = 'C'
else:
operation = 'U'
return super(AuditLogMixin, self).form_valid(form)
finally:
post_save_signal.send(sender=None,
instance=form.instance,
operation=operation,
request=self.request
)
class CrudCreateView(PermissionRequiredContainerCrudMixin, class CrudCreateView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, CreateView): FormMessagesMixin, AuditLogMixin, CreateView):
permission_required = (RP_ADD, ) permission_required = (RP_ADD, )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -837,7 +866,7 @@ class CrudDetailView(PermissionRequiredContainerCrudMixin,
class CrudUpdateView(PermissionRequiredContainerCrudMixin, class CrudUpdateView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, UpdateView): FormMessagesMixin, AuditLogMixin, UpdateView):
permission_required = (RP_CHANGE, ) permission_required = (RP_CHANGE, )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -868,7 +897,7 @@ class CrudUpdateView(PermissionRequiredContainerCrudMixin,
class CrudDeleteView(PermissionRequiredContainerCrudMixin, class CrudDeleteView(PermissionRequiredContainerCrudMixin,
FormMessagesMixin, DeleteView): FormMessagesMixin, AuditLogMixin, DeleteView):
permission_required = (RP_DELETE, ) permission_required = (RP_DELETE, )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

1
sapl/materia/forms.py

@ -24,6 +24,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from sapl.base.models import AppConfig, Autor, TipoAutor from sapl.base.models import AppConfig, Autor, TipoAutor
from sapl.base.signals import post_save_signal
from sapl.comissoes.models import Comissao, Composicao, Participacao from sapl.comissoes.models import Comissao, Composicao, Participacao
from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC, from sapl.compilacao.models import (STATUS_TA_IMMUTABLE_PUBLIC,
STATUS_TA_PRIVATE) STATUS_TA_PRIVATE)

17
sapl/materia/views.py

@ -33,13 +33,13 @@ from django_filters.views import FilterView
from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig from sapl.base.models import Autor, CasaLegislativa, AppConfig as BaseAppConfig
from sapl.base.signals import tramitacao_signal from sapl.base.signals import tramitacao_signal, post_delete_signal, post_save_signal
from sapl.comissoes.models import Comissao, Participacao, Composicao from sapl.comissoes.models import Comissao, Participacao, Composicao
from sapl.compilacao.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE from sapl.compilacao.models import STATUS_TA_IMMUTABLE_RESTRICT, STATUS_TA_PRIVATE
from sapl.compilacao.views import IntegracaoTaView from sapl.compilacao.views import IntegracaoTaView
from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout from sapl.crispy_layout_mixin import form_actions, SaplFormHelper, SaplFormLayout
from sapl.crud.base import (Crud, CrudAux, make_pagination, MasterDetailCrud, from sapl.crud.base import (Crud, CrudAux, make_pagination, MasterDetailCrud,
PermissionRequiredForAppCrudMixin, RP_DETAIL, RP_LIST) PermissionRequiredForAppCrudMixin, RP_DETAIL, RP_LIST,)
from sapl.materia.forms import (AnexadaForm, AutoriaForm, AutoriaMultiCreateForm, from sapl.materia.forms import (AnexadaForm, AutoriaForm, AutoriaMultiCreateForm,
ConfirmarProposicaoForm, DevolverProposicaoForm, ConfirmarProposicaoForm, DevolverProposicaoForm,
DespachoInicialCreateForm, LegislacaoCitadaForm, DespachoInicialCreateForm, LegislacaoCitadaForm,
@ -1374,7 +1374,7 @@ class TramitacaoCrud(MasterDetailCrud):
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
tramitacoes_deletar = [tramitacao.id] tramitacoes_deletar = [tramitacao]
if materia.tramitacao_set.count() == 0: if materia.tramitacao_set.count() == 0:
materia.em_tramitacao = False materia.em_tramitacao = False
materia.save() materia.save()
@ -1385,11 +1385,18 @@ class TramitacaoCrud(MasterDetailCrud):
for ma in mat_anexadas: for ma in mat_anexadas:
tram_anexada = ma.tramitacao_set.last() tram_anexada = ma.tramitacao_set.last()
if compara_tramitacoes_mat(tram_anexada, tramitacao): if compara_tramitacoes_mat(tram_anexada, tramitacao):
tramitacoes_deletar.append(tram_anexada.id) tramitacoes_deletar.append(tram_anexada)
if ma.tramitacao_set.count() == 0: if ma.tramitacao_set.count() == 0:
ma.em_tramitacao = False ma.em_tramitacao = False
ma.save() ma.save()
Tramitacao.objects.filter(id__in=tramitacoes_deletar).delete() Tramitacao.objects.filter(id__in=[t.id for t in tramitacoes_deletar]).delete()
# TODO: otimizar para passar a lista de matérias
for tramitacao in tramitacoes_deletar:
post_delete_signal.send(sender=None,
instance=tramitacao,
operation='C',
request=self.request)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

5
sapl/protocoloadm/forms.py

@ -2,6 +2,8 @@
import logging import logging
from crispy_forms.bootstrap import InlineRadios, Alert, FormActions from crispy_forms.bootstrap import InlineRadios, Alert, FormActions
from sapl.base.signals import post_save_signal
from sapl.crispy_layout_mixin import SaplFormHelper from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div, Submit from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div, Submit
from django import forms from django import forms
@ -1563,7 +1565,6 @@ class TramitacaoEmLoteAdmForm(ModelForm):
) )
) )
def clean(self): def clean(self):
cleaned_data = super(TramitacaoEmLoteAdmForm, self).clean() cleaned_data = super(TramitacaoEmLoteAdmForm, self).clean()
@ -1655,7 +1656,7 @@ class TramitacaoEmLoteAdmForm(ModelForm):
user=tramitacao.user, user=tramitacao.user,
ip=tramitacao.ip ip=tramitacao.ip
)) ))
TramitacaoAdministrativo.objects.bulk_create(lista_tramitacao) TramitacaoAdministrativo.objects.bulk_create(lista_tramitacao)
return tramitacao return tramitacao

15
sapl/protocoloadm/views.py

@ -26,7 +26,7 @@ from django_filters.views import FilterView
import sapl import sapl
from sapl.base.email_utils import do_envia_email_confirmacao from sapl.base.email_utils import do_envia_email_confirmacao
from sapl.base.models import Autor, CasaLegislativa, AppConfig from sapl.base.models import Autor, CasaLegislativa, AppConfig
from sapl.base.signals import tramitacao_signal from sapl.base.signals import tramitacao_signal, post_delete_signal
from sapl.comissoes.models import Comissao from sapl.comissoes.models import Comissao
from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, make_pagination, from sapl.crud.base import (Crud, CrudAux, MasterDetailCrud, make_pagination,
RP_LIST, RP_DETAIL) RP_LIST, RP_DETAIL)
@ -1268,7 +1268,7 @@ class TramitacaoAdmCrud(MasterDetailCrud):
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
tramitacoes_deletar = [tramitacao.id] tramitacoes_deletar = [tramitacao]
if documento.tramitacaoadministrativo_set.count() == 0: if documento.tramitacaoadministrativo_set.count() == 0:
documento.tramitacao = False documento.tramitacao = False
documento.save() documento.save()
@ -1278,12 +1278,19 @@ class TramitacaoAdmCrud(MasterDetailCrud):
for da in docs_anexados: for da in docs_anexados:
tram_anexada = da.tramitacaoadministrativo_set.last() tram_anexada = da.tramitacaoadministrativo_set.last()
if compara_tramitacoes_doc(tram_anexada, tramitacao): if compara_tramitacoes_doc(tram_anexada, tramitacao):
tramitacoes_deletar.append(tram_anexada.id) tramitacoes_deletar.append(tram_anexada)
if da.tramitacaoadministrativo_set.count() == 0: if da.tramitacaoadministrativo_set.count() == 0:
da.tramitacao = False da.tramitacao = False
da.save() da.save()
TramitacaoAdministrativo.objects.filter( TramitacaoAdministrativo.objects.filter(
id__in=tramitacoes_deletar).delete() id__in=[t.id for t in tramitacoes_deletar]).delete()
# TODO: otimizar para passar a lista de matérias
for tramitacao in tramitacoes_deletar:
post_delete_signal.send(sender=None,
instance=tramitacao,
operation='C',
request=self.request)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

1
sapl/rules/map_rules.py

@ -233,6 +233,7 @@ rules_group_geral = {
[RP_ADD], __perms_publicas__), [RP_ADD], __perms_publicas__),
(base.TipoAutor, __base__, __perms_publicas__), (base.TipoAutor, __base__, __perms_publicas__),
(base.Autor, __base__, __perms_publicas__), (base.Autor, __base__, __perms_publicas__),
(base.AuditLog, __base__, set()),
(protocoloadm.StatusTramitacaoAdministrativo, __base__, set()), (protocoloadm.StatusTramitacaoAdministrativo, __base__, set()),
(protocoloadm.TipoDocumentoAdministrativo, __base__, set()), (protocoloadm.TipoDocumentoAdministrativo, __base__, set()),

Loading…
Cancel
Save