Martín Miranda

Merge branch 'feature/#67_agregar_auditorias' into 'develop'

Feature/#67 agregar auditorias



See merge request !63
... ... @@ -4,6 +4,21 @@ from rest_framework.decorators import api_view
from django.conf import settings
import datetime
from actstream.models import actor_stream
from actstream.models import Action
from django.http import Http404
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticated
from rest_framework_json_api.views import ReadOnlyModelViewSet
from core.permissions import CustomModelPermissions
from core.serializers import ActionSerializer
from usuario.models import Usuario
@api_view(['POST'])
def recaptcha(request):
... ... @@ -16,3 +31,39 @@ def recaptcha(request):
)
return Response({'captcha': r.json()})
class AuditoriaViewSet(ReadOnlyModelViewSet):
queryset = Action.objects.all()
permission_classes = (IsAuthenticated, CustomModelPermissions)
serializer_class = ActionSerializer
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
ordering = '-timestamp'
def get_queryset(self):
queryset = super().get_queryset()
if not self.action == 'list':
return queryset
# en el caso de que la accion sea listar, controlar que se filtre por fecha obligatoriamente
usuario_id = self.request.GET.get('usuario_id', None)
fecha_desde = self.request.GET.get('fecha_desde', None)
fecha_hasta = self.request.GET.get('fecha_hasta', None)
if not fecha_desde:
return queryset.none()
if not fecha_hasta or fecha_hasta < fecha_desde:
fecha_hasta = datetime.datetime.now()
if usuario_id:
try:
usuario = get_object_or_404(Usuario, id=usuario_id)
queryset = usuario.actor_actions.public(timestamp__date__range=(fecha_desde, fecha_hasta))
except Http404:
return queryset.none()
else:
queryset = Action.objects.public(timestamp__date__range=(fecha_desde, fecha_hasta))
return queryset
... ...
# IMAGEN_AVATAR_DEFECTO = 'img/perfil-avatar.jpg'
TASK_TIME_LIMIT = 60 * 60
TASK_RETRY_DELAY = 60
# Verbos para auditoria
iniciado = 'iniciado'
... ...
from typing import Optional
from actstream import action
from actstream.models import Action
from django.contrib.contenttypes.models import ContentType
from rest_framework.response import Response
from rest_framework import status
from core.constants import iniciado
from core.serializers import ActionSerializer
class FiltroObligatorioMixin(object):
nombre_filtro_obligatorio: Optional[str] = None
def get_queryset(self):
queryset = super().get_queryset()
if not self.action == 'list':
return queryset
# en el caso de que la accion sea listar, controlar que vengan los datos del filtro indicado
filtro = self.request.GET.get(self.get_nombre_filtro_obligatorio(), None)
if filtro:
return queryset
return queryset.none()
def get_nombre_filtro_obligatorio(self):
assert self.nombre_filtro_obligatorio is not None, (
"Debe definir el atributo nombre_filtro_obligatorio a la clase '%s'"
% self.__class__.__name__
)
return self.nombre_filtro_obligatorio
class AuditoriaMixin:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
action_object = serializer.instance
self.registrar_accion(usuario=self.request.user, verb=iniciado, instance=serializer.instance,
data=serializer.data)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_update(self, serializer):
serializer_modificado = self.get_serializer(data=self.request.data)
serializer_modificado.is_valid()
serializer_accion = self.get_serializer(instance=serializer.instance)
data = {'data_inicial': serializer_accion.data, 'data_modificada': serializer_modificado.data}
self.registrar_accion(usuario=self.request.user, verb=serializer_modificado.data["estado"], instance=serializer.instance,
data=data)
serializer.save()
@staticmethod
def registrar_accion(usuario, verb, instance, data=None, target=None):
action.send(usuario, verb=verb, action_object=instance, target=target, data=data)
@staticmethod
def obtener_historial_acciones(content_type, action_objects_id, request):
content_type = ContentType.objects.get(model=content_type)
historial_actividades = Action.objects.filter(
action_object_object_id__in=action_objects_id,
action_object_content_type_id=content_type.id
)
serializer = ActionSerializer(historial_actividades, many=True, context={'request': request})
return serializer
... ...
from actstream.models import Action
from rest_framework import serializers
from usuario.serializers import UsuarioSerializer
from usuario.models import Usuario
class ActivityGenericRelatedField(serializers.Field):
"""
DRF Serializer field that serializers GenericForeignKey fields on the :class:`~activity.models.Action`
of known model types to their respective ActionSerializer implementation.
"""
def to_representation(self, value):
from edicto.serializers import EdictoSerializer, PrecioSerializer
MAPEO_SERIALIZADORES_POR_MODELO = {
"Usuario": UsuarioSerializer,
"Edicto": EdictoSerializer,
"Precio": PrecioSerializer
}
nombre_modelo = type(value).__name__
serializer_cls = MAPEO_SERIALIZADORES_POR_MODELO.get(nombre_modelo, None)
if serializer_cls:
# se genera un nuevo diccionario donde se le suma otro diccionario conteniendo el elemento "type"
data = {**{'type': nombre_modelo}, **{'id': value.id}, **serializer_cls(value, context=self.context).data}
else:
data = str(value)
return data
class ActorSerializer(serializers.ModelSerializer):
class Meta:
model = Usuario
fields = (
'username',
'first_name',
'last_name',
'cuil'
)
class ActionSerializer(serializers.Serializer):
"""
DRF serializer for :class:`~activity.models.Action`.
"""
actor = ActorSerializer(read_only=True)
verb = serializers.CharField(read_only=True)
action_object = ActivityGenericRelatedField(read_only=True)
target = ActivityGenericRelatedField(read_only=True)
timestamp = serializers.DateTimeField(read_only=True)
data = serializers.DictField(read_only=True)
class Meta:
model = Action
fields = ('id', 'actor', 'verb', 'action_object', 'target', 'timestamp', 'data')
... ...
... ... @@ -2,13 +2,18 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets, filters, mixins
from rest_framework.permissions import IsAuthenticated
from core.mixins import AuditoriaMixin
from rest_framework.decorators import action
from core.serializers import ActionSerializer
from rest_framework.response import Response
from .filters import EdictoFilter, PrecioFilter
from .models import Edicto, Precio
from .permissions import IsAdminOrAuthorized
from .serializer import EdictoSerializer, PrecioSerializer
from .serializers import EdictoSerializer, PrecioSerializer
class EdictoViewSet(mixins.CreateModelMixin,
class EdictoViewSet(AuditoriaMixin, mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
... ... @@ -23,8 +28,23 @@ class EdictoViewSet(mixins.CreateModelMixin,
ordering = ('fecha_publicacion',)
lookup_field = 'uuid'
@action(
methods=['GET'],
detail=True,
url_path='obtener-historial',
serializer_class=ActionSerializer
)
def obtener_historial(self, request, uuid):
action_objects_id = list(Edicto.objects.filter(uuid=uuid).values_list('id', flat=True))
serializer = self.obtener_historial_acciones(
content_type="edicto",
action_objects_id=action_objects_id,
request=request
)
return Response(serializer.data)
class PrecioViewSet(viewsets.ReadOnlyModelViewSet):
class PrecioViewSet(AuditoriaMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = PrecioSerializer
permission_classes = [IsAuthenticated, IsAdminOrAuthorized]
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
... ...
... ... @@ -4,3 +4,13 @@ from django.apps import AppConfig
class EdictoConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'edicto'
def ready(self):
from actstream import registry
registry.register(
self.get_model('Edicto'),
self.get_model('Precio'),
)
default_app_config = 'edicto.apps.EdictoConfig'
... ...
... ... @@ -4,3 +4,10 @@ from django.apps import AppConfig
class UsuarioConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'usuario'
def ready(self):
from actstream import registry
registry.register(self.get_model('Usuario'))
default_app_config = 'usuario.apps.UsuarioConfig'
... ...
... ... @@ -4,6 +4,8 @@ from organismo import api as organismo_api
from usuario import api as usuario_api
from edicto.api import EdictoViewSet
from edicto import api as edicto_api
from core import api as core_api
# Define routes
router = routers.DefaultRouter()
... ... @@ -11,5 +13,4 @@ router.register(prefix='usuario', viewset=usuario_api.UsuarioViewSet)
router.register(prefix='organismo', viewset=organismo_api.OrganismoViewSet)
router.register(r'edicto', EdictoViewSet, basename='edicto')
router.register(prefix='precio', viewset=edicto_api.PrecioViewSet)
router.register(prefix='auditoria', viewset=core_api.AuditoriaViewSet)
... ...
... ... @@ -52,6 +52,7 @@ THIRD_PARTY_APPS = (
'django_filters',
'corsheaders',
'oauth2_provider',
'actstream',
)
PROJECT_APPS = (
... ... @@ -61,6 +62,8 @@ PROJECT_APPS = (
'edicto',
)
SITE_ID = 1
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + PROJECT_APPS
MIDDLEWARE = (
... ... @@ -192,3 +195,7 @@ AUTHENTICATION_BACKENDS = (
# Secret Key para Captcha.
SECRET_KEY_CAPTCHA = env.str('SECRET_KEY_CAPTCHA', default="")
ACTSTREAM_SETTINGS = {
'USE_JSONFIELD': True,
}
\ No newline at end of file
... ...
... ... @@ -19,3 +19,6 @@ pyOpenSSL==22.0.0
# database
psycopg2==2.9.1
psycopg2-binary==2.9.1
# Django Activity
django-activity-stream==1.4.2
\ No newline at end of file
... ...