Niveles de permiso#

Sistema de permisos de seis niveles (NIVELES_ACCESO) para control de acceso.

Descripción general#

Seis niveles de permiso:

  1. Auxiliar (1) - Ver solo

  2. Coordinador (2) - Crear y editar

  3. Director (3) - Operaciones CRUD completas

  4. Corporativo (4) - Acceso entre empresas

  5. Soporte (5) - Soporte/mantenimiento

  6. Superusuario (6) - Acceso completo al sistema

Implementación: Clave externa en modelo de Usuario apuntando a NivelAcceso.

Modelo NivelAcceso#

Archivo: construbot/users/models.py

class NivelAcceso(models.Model):
    """Permission level definition"""

    nombre = models.CharField(max_length=50)  # Name
    nivel = models.IntegerField(unique=True)  # Level number (1-6)
    descripcion = models.TextField(blank=True)

    class Meta:
        ordering = ['nivel']
        verbose_name_plural = 'Niveles de Acceso'

    def __str__(self):
        return f"{self.nivel} - {self.nombre}"

Niveles predeterminados:

# Populated via migration or fixtures
1 - Auxiliar
2 - Coordinador
3 - Director
4 - Corporativo
5 - Soporte
6 - Superusuario

Asignación de usuarios#

class User(AbstractUser):
    nivel_acceso = models.ForeignKey(
        NivelAcceso,
        on_delete=models.PROTECT,
        related_name='users'
    )

Nivel de configuración:

user = User.objects.get(email='user@example.com')
user.nivel_acceso = NivelAcceso.objects.get(nivel=3)  # Director
user.save()

Verificaciones de permisos#

Ver decoradores#

def nivel_required(min_level):
    """Decorator to require minimum permission level"""
    def decorator(view_func):
        @wraps(view_func)
        def wrapper(request, *args, **kwargs):
            if not request.user.is_authenticated:
                return redirect('login')
            if request.user.nivel_acceso.nivel < min_level:
                raise PermissionDenied(f"Requires level {min_level}")
            return view_func(request, *args, **kwargs)
        return wrapper
    return decorator

# Usage
@nivel_required(3)  # Director or higher
def delete_contract(request, contract_id):
    ...

Vistas basadas en clases#

class NivelRequiredMixin:
    """Mixin for permission level check"""
    required_nivel = 1  # Override in subclass

    def dispatch(self, request, *args, **kwargs):
        if request.user.nivel_acceso.nivel < self.required_nivel:
            raise PermissionDenied()
        return super().dispatch(request, *args, **kwargs)

class ContractDeleteView(NivelRequiredMixin, DeleteView):
    required_nivel = 3  # Director
    model = Contrato

Verificaciones de plantilla#

{% if user.nivel_acceso.nivel >= 3 %}
    <a href="{% url 'delete_contract' contract.id %}">Delete</a>
{% endif %}

{% if user.nivel_acceso.nivel == 6 %}
    <div class="admin-panel">...</div>
{% endif %}

Capacidades específicas de nivel#

1. Auxiliar (solo ver)#

Permisos:

  • Ver contratos, estimaciones, informes.

  • No crear/editar/eliminar

Caso de uso: Trabajadores de campo, acceso de solo lectura

@nivel_required(1)
def view_contract(request, contract_id):
    # Can view but not edit

2. Coordinador (Crear/Editar)#

Permisos:

  • Todos los permisos auxiliares

  • Crear nuevos registros

  • Editar registros existentes

  • No se puede eliminar

Caso de uso: Coordinadores de proyecto

@nivel_required(2)
def create_estimate(request):
    # Can create estimates

3. Director (CRUD completo)#

Permisos:

  • Todos los permisos del Coordinador

  • Eliminar registros

  • Aprobar estimaciones

  • Generar informes

Caso de uso: Gerentes de proyecto, directores

@nivel_required(3)
def delete_contract(request, contract_id):
    # Can delete contracts

4. Corporativo (Multiempresa)#

Permisos:

  • Todos los permisos del director

  • Accede a múltiples empresas

  • Informes entre empresas

  • Configuración a nivel de empresa

Caso de uso: Gestión a nivel corporativo

@nivel_required(4)
def corporate_report(request):
    # Access all companies under customer
    companies = Company.objects.filter(customer=request.user.customer)

5. Soporte (Acceso de soporte)#

Permisos:

  • Acceso al soporte técnico

  • Diagnóstico del sistema

  • Asistencia al usuario

  • Configuración limitada

Caso de uso: Personal de soporte técnico

@nivel_required(5)
def system_diagnostics(request):
    # Support-only features

6. Superusuario (Acceso Completo)#

Permisos:

  • Todo el acceso al sistema.

  • Administrador de Django

  • Gestión de usuarios

  • Configuración del sistema

Caso de uso: Administradores del sistema

# Django superuser check
if request.user.is_superuser:
    # Full access

Combinando con el alcance de la empresa#

Combine siempre la verificación de nivel con el alcance de la empresa:

@login_required
@nivel_required(2)
def create_contract(request):
    # Level check passed
    company = request.user.active_company

    if request.method == 'POST':
        contract = form.save(commit=False)
        contract.company = company  # Company scope
        contract.save()

Permisos API#

from rest_framework.permissions import BasePermission

class HasNivelAcceso(BasePermission):
    """DRF permission for nivel check"""

    def has_permission(self, request, view):
        required_level = getattr(view, 'required_nivel', 1)
        return request.user.nivel_acceso.nivel >= required_level

# Usage
class ContractViewSet(viewsets.ModelViewSet):
    required_nivel = 3
    permission_classes = [IsAuthenticated, HasNivelAcceso]

Mejores prácticas#

1. Utilice el nivel mínimo requerido:

# GOOD - requires minimum needed
@nivel_required(2)
def edit_view(request):
    ...

# BAD - unnecessarily restrictive
@nivel_required(6)  # Unless truly needed

2. Verifique vistas, no plantillas:

# GOOD - security in view
@nivel_required(3)
def delete_view(request):
    ...

# BAD - relying only on template hiding
# <a href="delete">Delete</a>  (no view protection)

3. Combinar cheques:

@login_required
@nivel_required(3)
def delete_contract(request, contract_id):
    contract = get_object_or_404(Contrato, id=contract_id)

    # Also verify company access
    if contract.company != request.user.active_company:
        raise PermissionDenied()

    contract.delete()

4. Niveles de documentación requeridos:

@nivel_required(3)
def delete_contract(request, contract_id):
    """
    Delete a contract.

    Requires: Director level (3) or higher
    """

Solución de problemas#

Errores de permiso denegado:

  • Consultar nivel_acceso del usuario

  • Verifique que el nivel requerido sea correcto

  • Confirmar que el usuario está autenticado

El usuario no puede acceder a la función:

  • Consultar asignación nivel_acceso

  • Verificar que se haya aplicado el decorador de permisos

  • Confirmar que el umbral de nivel es apropiado

Ver también#