235 lines
No EOL
8.6 KiB
Python
235 lines
No EOL
8.6 KiB
Python
from django.shortcuts import get_object_or_404
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.db.models import Q
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from functools import wraps
|
|
|
|
from projects.models import Project, ProjectTeamAccess, ProjectUserAccess
|
|
from common.models import UserConfig, UserThematics
|
|
|
|
|
|
def _has_explicit_view_access(user, project, user_config):
|
|
"""Vérifie uniquement les accès explicites (équipe ou utilisateur direct)."""
|
|
from contracts.models import CompanyMember
|
|
team_ids = CompanyMember.objects.filter(
|
|
user=user
|
|
).values_list('teams__id', flat=True)
|
|
if team_ids and ProjectTeamAccess.objects.filter(
|
|
project=project, team__in=team_ids, can_view=True
|
|
).exists():
|
|
return True
|
|
return ProjectUserAccess.objects.filter(project=project, user=user, can_view=True).exists()
|
|
|
|
|
|
def can_view_project(user, project):
|
|
try:
|
|
user_config = UserConfig.objects.get(user=user)
|
|
except UserConfig.DoesNotExist:
|
|
return False
|
|
|
|
# Les admins et top_managers voient tout, y compris les projets en accès strict
|
|
if user_config.roles.filter(name__in=['admin', 'top_manager']).exists():
|
|
return True
|
|
|
|
# En accès strict : seuls les accès explicites (équipe/utilisateur) sont pris en compte
|
|
if project.strict_access:
|
|
return _has_explicit_view_access(user, project, user_config)
|
|
|
|
# Vérifie si l'utilisateur est interne ou viewer et a accès à la thématique (permission projet dédiée)
|
|
thematics = project.thematics.all() if project.thematics.exists() else None
|
|
if thematics and (user_config.is_intern or user_config.has_role('viewer')):
|
|
if UserThematics.objects.filter(
|
|
user_config=user_config,
|
|
thematic__in=thematics,
|
|
can_view_projects=True
|
|
).exists():
|
|
return True
|
|
|
|
# Accès explicites (équipe ou utilisateur)
|
|
return _has_explicit_view_access(user, project, user_config)
|
|
|
|
def project_permission_required(view_func):
|
|
@wraps(view_func)
|
|
def _wrapped_view(request, project_id, *args, **kwargs):
|
|
project = get_object_or_404(Project, pk=project_id)
|
|
if not can_view_project(request.user, project):
|
|
raise PermissionDenied(_("Vous n'avez pas la permission de voir ce projet."))
|
|
return view_func(request, project_id, *args, **kwargs)
|
|
return _wrapped_view
|
|
|
|
|
|
|
|
def can_add_or_edit_project(user, project=None, ignore_status_lock=False):
|
|
try:
|
|
user_config = UserConfig.objects.get(user=user)
|
|
except UserConfig.DoesNotExist:
|
|
return False
|
|
|
|
# Verrouille l'édition d'attributs quand le projet est comptabilisé/clos/annulé,
|
|
# sauf si l'appel demande explicitement d'ignorer ce verrou (ex: changement de statut).
|
|
if project and not ignore_status_lock and project.status in ['invoiced', 'closed', 'canceled']:
|
|
return False
|
|
|
|
# Les admins peuvent tout modifier
|
|
if user_config.roles.filter(name__in=['admin']).exists():
|
|
return True
|
|
|
|
# Les externes ne peuvent pas modifier les propriétés d'un projet
|
|
if not user_config.is_intern:
|
|
return False
|
|
|
|
# Vérifie les accès explicites en édition (équipe ou utilisateur direct)
|
|
if project:
|
|
from contracts.models import CompanyMember
|
|
team_ids = CompanyMember.objects.filter(
|
|
user=user
|
|
).values_list('teams__id', flat=True)
|
|
if team_ids and ProjectTeamAccess.objects.filter(
|
|
project=project, team__in=team_ids, can_edit=True
|
|
).exists():
|
|
return True
|
|
if ProjectUserAccess.objects.filter(project=project, user=user, can_edit=True).exists():
|
|
return True
|
|
|
|
# En accès strict : les permissions thématiques ne s'appliquent pas
|
|
if project and project.strict_access:
|
|
return False
|
|
|
|
# Vérifie si l'utilisateur est interne, a le rôle manager et a accès à la thématique du projet
|
|
if project and project.thematics and user_config.is_intern and user_config.roles.filter(name='manager').exists():
|
|
if UserThematics.objects.filter(
|
|
user_config=user_config,
|
|
thematic__in=project.thematics.all(),
|
|
can_edit_projects=True
|
|
).exists():
|
|
return True
|
|
|
|
if not project and user_config.is_intern and user_config.roles.filter(name='manager').exists():
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def can_add_project_documents(user, project):
|
|
"""Autorise l'ajout de documents : édition requise pour les internes, vue seule suffit pour les externes."""
|
|
if not can_view_project(user, project):
|
|
return False
|
|
try:
|
|
user_config = UserConfig.objects.get(user=user)
|
|
except UserConfig.DoesNotExist:
|
|
return False
|
|
if not user_config.is_intern:
|
|
return True
|
|
return can_add_or_edit_project(user, project, ignore_status_lock=True)
|
|
|
|
|
|
def can_manage_project_orders(user, project=None):
|
|
"""Admins ou éditeurs du projet uniquement."""
|
|
try:
|
|
user_config = UserConfig.objects.get(user=user)
|
|
except UserConfig.DoesNotExist:
|
|
return False
|
|
|
|
if not user_config.roles.filter(name__in=['admin', 'manager']).exists():
|
|
return False
|
|
|
|
if project:
|
|
return can_add_or_edit_project(user, project)
|
|
|
|
return True
|
|
|
|
|
|
def can_manage_project_members(user, project):
|
|
"""Seul un utilisateur ayant des droits d'édition sur le projet peut gérer les accès."""
|
|
if not can_view_project(user, project):
|
|
return False
|
|
|
|
return can_add_or_edit_project(user, project)
|
|
|
|
|
|
def project_manage_members_permission_required(view_func):
|
|
@wraps(view_func)
|
|
def _wrapped_view(request, project_id, *args, **kwargs):
|
|
project = get_object_or_404(Project, pk=project_id)
|
|
if not can_manage_project_members(request.user, project):
|
|
raise PermissionDenied(_("Seul le responsable du projet ou un manager peut gérer les membres."))
|
|
return view_func(request, project_id, *args, **kwargs)
|
|
return _wrapped_view
|
|
|
|
def project_add_or_edit_permission_required(view_func):
|
|
@wraps(view_func)
|
|
def _wrapped_view(request, *args, **kwargs):
|
|
project = None
|
|
|
|
# Vérifie si 'project_id' est fourni dans les paramètres de la vue
|
|
project_id = kwargs.get('project_id')
|
|
if project_id is not None:
|
|
project = get_object_or_404(Project, pk=project_id)
|
|
|
|
if not can_add_or_edit_project(request.user, project):
|
|
raise PermissionDenied(_("Vous n'avez pas la permission d'ajouter ou de modifier ce projet."))
|
|
|
|
return view_func(request, *args, **kwargs)
|
|
|
|
return _wrapped_view
|
|
|
|
|
|
|
|
|
|
def filter_viewable_projects_for_user(user):
|
|
"""
|
|
Renvoie un queryset des projets visibles par l'utilisateur
|
|
selon les règles de can_view_project().
|
|
"""
|
|
try:
|
|
user_config = UserConfig.objects.select_related('user').prefetch_related('roles', 'thematics').get(user=user)
|
|
except UserConfig.DoesNotExist:
|
|
return Project.objects.none()
|
|
|
|
projects = Project.objects.all()
|
|
|
|
# Rôles admin ou top_manager → accès global
|
|
if user_config.roles.filter(name__in=['admin', 'top_manager']).exists():
|
|
return projects
|
|
|
|
from contracts.models import CompanyMember
|
|
team_ids = list(CompanyMember.objects.filter(
|
|
user=user
|
|
).values_list('teams__id', flat=True))
|
|
|
|
# Accès explicites (équipe ou utilisateur) — valables même en accès strict
|
|
q_explicit = Q(user_accesses__user=user, user_accesses__can_view=True)
|
|
if team_ids:
|
|
q_explicit |= Q(team_accesses__team__in=team_ids, team_accesses__can_view=True)
|
|
|
|
# Accès via thématique (uniquement pour les projets sans accès strict)
|
|
q_thematic = Q()
|
|
if user_config.is_intern or user_config.has_role('viewer'):
|
|
thematic_ids = list(UserThematics.objects.filter(
|
|
user_config=user_config,
|
|
can_view_projects=True
|
|
).values_list('thematic__id', flat=True))
|
|
if thematic_ids:
|
|
q_thematic = Q(strict_access=False, thematics__id__in=thematic_ids)
|
|
|
|
return projects.filter(q_explicit | q_thematic).distinct()
|
|
|
|
|
|
# =============================================================================
|
|
# Guard functions for private file access
|
|
# =============================================================================
|
|
|
|
def guard_project_document_file(obj, user):
|
|
"""
|
|
Guard function for ProjectDocument.file and ProjectDocument.thumbnail fields.
|
|
Checks if user has access to the project.
|
|
"""
|
|
from .models import ProjectDocument
|
|
if not isinstance(obj, ProjectDocument):
|
|
return False
|
|
|
|
if not user.is_authenticated:
|
|
return False
|
|
|
|
return can_view_project(user, obj.project) |