Mudanza de repo
This commit is contained in:
0
api/notificaciones/__init__.py
Normal file
0
api/notificaciones/__init__.py
Normal file
20
api/notificaciones/admin.py
Normal file
20
api/notificaciones/admin.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.contrib import admin
|
||||
from .models import Notificacion, TipoNotificacion
|
||||
# Register your models here.
|
||||
|
||||
|
||||
class NotificacionAdmin(admin.ModelAdmin):
|
||||
list_display = ('tipo', 'dirigido', 'mensaje', 'fecha_envio', 'created_at', 'visto')
|
||||
search_fields = ('mensaje', 'tipo__tipo', 'dirigido__username')
|
||||
list_filter = ('tipo', 'visto', 'fecha_envio')
|
||||
ordering = ('-created_at',)
|
||||
date_hierarchy = 'fecha_envio'
|
||||
|
||||
class TipoNotificacionAdmin(admin.ModelAdmin):
|
||||
list_display = ('tipo', 'descripcion')
|
||||
search_fields = ('tipo',)
|
||||
ordering = ('tipo',)
|
||||
|
||||
admin.site.register(Notificacion, NotificacionAdmin)
|
||||
admin.site.register(TipoNotificacion, TipoNotificacionAdmin)
|
||||
admin.site.empty_value_display = '-vacío-' # Display this when a field is empty
|
||||
11
api/notificaciones/apps.py
Normal file
11
api/notificaciones/apps.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class NotificacionesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'api.notificaciones'
|
||||
|
||||
def ready(self):
|
||||
import api.notificaciones.signals.notificaciones
|
||||
# Import other signals if needed
|
||||
# import api.notificaciones.signals.other_signal
|
||||
0
api/notificaciones/consumers.py
Normal file
0
api/notificaciones/consumers.py
Normal file
49
api/notificaciones/migrations/0001_initial.py
Normal file
49
api/notificaciones/migrations/0001_initial.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# Generated by Django 5.2.3 on 2025-07-14 16:14
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TipoNotificacion',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('tipo', models.CharField(help_text='Tipo de notificación', max_length=100, unique=True)),
|
||||
('descripcion', models.CharField(help_text='Descripción del tipo de notificación', max_length=200)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Tipo de Notificación',
|
||||
'verbose_name_plural': 'Tipos de Notificación',
|
||||
'db_table': 'tipo_notificacion',
|
||||
'ordering': ['tipo'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Notificacion',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('mensaje', models.TextField(help_text='Mensaje de la notificación')),
|
||||
('fecha_envio', models.DateTimeField(blank=True, help_text='Fecha de envío de la notificación', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, help_text='Fecha de creación de la notificación')),
|
||||
('visto', models.BooleanField(default=False, help_text='Indica si la notificación ha sido vista')),
|
||||
('dirigido', models.ForeignKey(help_text='Usuario al que se dirige la notificación', on_delete=django.db.models.deletion.CASCADE, related_name='notificaciones', to=settings.AUTH_USER_MODEL)),
|
||||
('tipo', models.ForeignKey(help_text='Tipo de notificación', on_delete=django.db.models.deletion.CASCADE, related_name='notificaciones', to='notificaciones.tiponotificacion')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Notificación',
|
||||
'verbose_name_plural': 'Notificaciones',
|
||||
'db_table': 'notificaciones',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
api/notificaciones/migrations/__init__.py
Normal file
0
api/notificaciones/migrations/__init__.py
Normal file
35
api/notificaciones/models.py
Normal file
35
api/notificaciones/models.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from django.db import models
|
||||
from api.cuser.models import CustomUser
|
||||
# Create your models here.
|
||||
|
||||
class TipoNotificacion(models.Model):
|
||||
tipo = models.CharField(max_length=100, unique=True, help_text="Tipo de notificación")
|
||||
descripcion = models.CharField(max_length=200, help_text="Descripción del tipo de notificación")
|
||||
|
||||
def __str__(self):
|
||||
return self.tipo
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Tipo de Notificación"
|
||||
verbose_name_plural = "Tipos de Notificación"
|
||||
db_table = 'tipo_notificacion'
|
||||
ordering = ['tipo']
|
||||
|
||||
class Notificacion(models.Model):
|
||||
tipo = models.ForeignKey(TipoNotificacion, on_delete=models.CASCADE, related_name='notificaciones', help_text="Tipo de notificación")
|
||||
dirigido = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='notificaciones', help_text="Usuario al que se dirige la notificación")
|
||||
|
||||
|
||||
mensaje = models.TextField(help_text="Mensaje de la notificación")
|
||||
fecha_envio = models.DateTimeField(blank=True, null=True, help_text="Fecha de envío de la notificación")
|
||||
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación de la notificación")
|
||||
visto = models.BooleanField(default=False, help_text="Indica si la notificación ha sido vista")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.tipo} - {self.created_at.strftime('%Y-%m-%d %H:%M:%S')}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Notificación"
|
||||
verbose_name_plural = "Notificaciones"
|
||||
db_table = 'notificaciones'
|
||||
ordering = ['-created_at']
|
||||
0
api/notificaciones/routing.py
Normal file
0
api/notificaciones/routing.py
Normal file
25
api/notificaciones/serializers.py
Normal file
25
api/notificaciones/serializers.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Notificacion, TipoNotificacion
|
||||
|
||||
class TipoNotificacionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = TipoNotificacion
|
||||
fields = ['id', 'tipo', 'descripcion']
|
||||
read_only_fields = ['id', 'tipo', 'descripcion']
|
||||
|
||||
|
||||
class NotificacionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Notificacion
|
||||
fields = [
|
||||
'id',
|
||||
'tipo',
|
||||
'dirigido',
|
||||
'mensaje',
|
||||
'fecha_envio',
|
||||
'created_at',
|
||||
'visto'
|
||||
]
|
||||
read_only_fields = ['id', 'created_at', 'tipo', 'dirigido', 'fecha_envio', 'mensaje']
|
||||
|
||||
|
||||
0
api/notificaciones/signals/__init__.py
Normal file
0
api/notificaciones/signals/__init__.py
Normal file
34
api/notificaciones/signals/notificaciones.py
Normal file
34
api/notificaciones/signals/notificaciones.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from api.notificaciones.models import Notificacion
|
||||
from api.record.models import Document
|
||||
|
||||
@receiver(post_save, sender=Document)
|
||||
def trigger_notificacion(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
from api.cuser.models import CustomUser
|
||||
from api.customs.models import Pedimento
|
||||
from api.notificaciones.models import TipoNotificacion
|
||||
|
||||
# Obtener el tipo de notificación (puedes ajustar el nombre si tienes tipos definidos)
|
||||
tipo_info, _ = TipoNotificacion.objects.get_or_create(tipo="info", defaults={"descripcion": "Notificación informativa"})
|
||||
|
||||
# Notificar a todos los usuarios de la organización
|
||||
usuarios_org = CustomUser.objects.filter(organizacion=instance.organizacion)
|
||||
for usuario in usuarios_org:
|
||||
# Notificar solo a importadores cuyo RFC coincide
|
||||
if (usuario.is_importador or usuario.groups.filter(name='Importador').exists()):
|
||||
if usuario.rfc == instance.pedimento.contribuyente:
|
||||
Notificacion.objects.create(
|
||||
tipo=tipo_info,
|
||||
dirigido=usuario,
|
||||
mensaje=f"Se agregó el documento {instance.archivo} al pedimento {instance.pedimento.pedimento} \n {instance.document_type.nombre}",
|
||||
)
|
||||
# Notificar a otros roles (no importadores)
|
||||
elif (usuario.is_superuser or usuario.groups.filter(name='Agente Aduanal').exists() or usuario.groups.filter(name='admin').exists()):
|
||||
Notificacion.objects.create(
|
||||
tipo=tipo_info,
|
||||
dirigido=usuario,
|
||||
mensaje=f"Se agregó el documento {instance.archivo} al pedimento {instance.pedimento.pedimento} \n {instance.document_type.nombre}",
|
||||
)
|
||||
77
api/notificaciones/tests.py
Normal file
77
api/notificaciones/tests.py
Normal file
@@ -0,0 +1,77 @@
|
||||
|
||||
from django.urls import reverse
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
from rest_framework import status
|
||||
from django.contrib.auth import get_user_model
|
||||
from .models import Notificacion, TipoNotificacion
|
||||
from api.organization.models import Organizacion
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
class NotificacionesViewSetTests(APITestCase):
|
||||
def setUp(self):
|
||||
self.org = Organizacion.objects.create(nombre="OrgTest", is_active=True, is_verified=True)
|
||||
self.org2 = Organizacion.objects.create(nombre="OrgTest2", is_active=True, is_verified=True)
|
||||
self.admin = User.objects.create_user(username="admin", password="adminpass", organizacion=self.org)
|
||||
self.admin.groups.create(name="admin")
|
||||
self.superuser = User.objects.create_superuser(username="superuser", password="superpass")
|
||||
self.importador = User.objects.create_user(username="importador", password="importpass", organizacion=self.org2, is_importador=True, rfc="RFC123456789")
|
||||
self.importador.groups.create(name="importador")
|
||||
self.client = APIClient()
|
||||
|
||||
def test_list_tipo_notificacion(self):
|
||||
TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
url = reverse('tipo-notificacion-list')
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertTrue(len(response.data) >= 1)
|
||||
|
||||
def test_admin_sees_only_org_notificaciones(self):
|
||||
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
|
||||
notif1 = Notificacion.objects.create(tipo=tipo, dirigido=self.admin, mensaje="msg1")
|
||||
notif2 = Notificacion.objects.create(tipo=tipo, dirigido=self.importador, mensaje="msg2")
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
url = reverse('notificacion-list')
|
||||
response = self.client.get(url)
|
||||
mensajes = [n['mensaje'] for n in response.data]
|
||||
self.assertIn("msg1", mensajes)
|
||||
self.assertNotIn("msg2", mensajes)
|
||||
|
||||
def test_superuser_sees_all_notificaciones(self):
|
||||
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
|
||||
notif1 = Notificacion.objects.create(tipo=tipo, dirigido=self.admin, mensaje="msg1")
|
||||
notif2 = Notificacion.objects.create(tipo=tipo, dirigido=self.importador, mensaje="msg2")
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
url = reverse('notificacion-list')
|
||||
response = self.client.get(url)
|
||||
mensajes = [n['mensaje'] for n in response.data]
|
||||
self.assertIn("msg1", mensajes)
|
||||
self.assertIn("msg2", mensajes)
|
||||
|
||||
def test_importador_sees_only_own_notificaciones(self):
|
||||
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
|
||||
notif1 = Notificacion.objects.create(tipo=tipo, dirigido=self.admin, mensaje="msg1")
|
||||
notif2 = Notificacion.objects.create(tipo=tipo, dirigido=self.importador, mensaje="msg2")
|
||||
self.client.force_authenticate(user=self.importador)
|
||||
url = reverse('notificacion-list')
|
||||
response = self.client.get(url)
|
||||
mensajes = [n['mensaje'] for n in response.data]
|
||||
self.assertNotIn("msg1", mensajes)
|
||||
self.assertIn("msg2", mensajes)
|
||||
|
||||
def test_superuser_can_create_notificacion(self):
|
||||
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
url = reverse('notificacion-list')
|
||||
data = {"tipo": tipo.id, "dirigido": self.admin.id, "mensaje": "msg3"}
|
||||
response = self.client.post(url, data)
|
||||
self.assertIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_200_OK])
|
||||
|
||||
def test_admin_cannot_create_notificacion(self):
|
||||
tipo = TipoNotificacion.objects.create(tipo="info", descripcion="informativa")
|
||||
self.client.force_authenticate(user=self.admin)
|
||||
url = reverse('notificacion-list')
|
||||
data = {"tipo": tipo.id, "dirigido": self.importador.id, "mensaje": "msg4"}
|
||||
response = self.client.post(url, data)
|
||||
self.assertNotIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_200_OK])
|
||||
13
api/notificaciones/urls.py
Normal file
13
api/notificaciones/urls.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from rest_framework import routers
|
||||
from django.urls import path, include
|
||||
from .views import TipoNotificacionViewSet, NotificacionViewSet
|
||||
|
||||
# Create a router and register the viewsets
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r'tipos', TipoNotificacionViewSet, basename='tipo-notificacion')
|
||||
router.register(r'notificaciones', NotificacionViewSet, basename='notificacion')
|
||||
|
||||
# Create a router and register the healthcheck view
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
50
api/notificaciones/views.py
Normal file
50
api/notificaciones/views.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from django.shortcuts import render
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from .models import Notificacion, TipoNotificacion
|
||||
from .serializers import NotificacionSerializer, TipoNotificacionSerializer
|
||||
from core.permissions import (
|
||||
IsSameOrganization,
|
||||
IsSameOrganizationDeveloper,
|
||||
IsSameOrganizationAndAdmin,
|
||||
IsSuperUser
|
||||
)
|
||||
# Create your views here.
|
||||
|
||||
class TipoNotificacionViewSet(viewsets.ModelViewSet):
|
||||
queryset = TipoNotificacion.objects.all()
|
||||
serializer_class = TipoNotificacionSerializer
|
||||
http_method_names = ['get']
|
||||
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
|
||||
my_tags = ['Notificaciones']
|
||||
|
||||
def get_queryset(self):
|
||||
return self.queryset.order_by('tipo')
|
||||
|
||||
class NotificacionViewSet(viewsets.ModelViewSet):
|
||||
queryset = Notificacion.objects.all()
|
||||
serializer_class = NotificacionSerializer
|
||||
http_method_names = ['get', 'post', 'put', 'patch', 'delete']
|
||||
filterset_fields = ['visto']
|
||||
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
my_tags = ['Notificaciones']
|
||||
|
||||
def get_queryset(self):
|
||||
# Evita error en generación de esquema Swagger
|
||||
if getattr(self, 'swagger_fake_view', False):
|
||||
return Notificacion.objects.none()
|
||||
user = self.request.user
|
||||
if not user.is_authenticated:
|
||||
return Notificacion.objects.none()
|
||||
return Notificacion.objects.filter(dirigido=user)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
if not self.request.user.is_authenticated:
|
||||
raise PermissionDenied("Usuario no autenticado")
|
||||
if self.request.user.is_superuser:
|
||||
# Allow superusers and admins to create notifications for any user
|
||||
serializer.save()
|
||||
raise PermissionDenied("No tienes permiso para crear notificaciones para otros usuarios")
|
||||
Reference in New Issue
Block a user