268 lines
No EOL
10 KiB
Python
268 lines
No EOL
10 KiB
Python
from django import forms
|
||
from django.forms import Textarea, DateInput
|
||
from django.contrib.auth import get_user_model
|
||
from django.utils.translation import gettext_lazy as _
|
||
from django.db.models import Q
|
||
|
||
from .models import Project, ProjectUserAccess, ProjectTeamAccess, ProjectDocument, ProjectPhase
|
||
from common.models import UserConfig
|
||
|
||
User = get_user_model()
|
||
|
||
|
||
class MultipleFileInput(forms.ClearableFileInput):
|
||
allow_multiple_selected = True
|
||
|
||
|
||
class MultipleFileField(forms.FileField):
|
||
def __init__(self, *args, **kwargs):
|
||
kwargs.setdefault("widget", MultipleFileInput())
|
||
super().__init__(*args, **kwargs)
|
||
|
||
def clean(self, data, initial=None):
|
||
single_file_clean = super().clean
|
||
if isinstance(data, (list, tuple)):
|
||
result = [single_file_clean(d, initial) for d in data]
|
||
else:
|
||
result = [single_file_clean(data, initial)]
|
||
return result
|
||
|
||
|
||
class ProjectUserAccessForm(forms.ModelForm):
|
||
user = forms.ModelChoiceField(
|
||
queryset=User.objects.none(),
|
||
label=_("Utilisateur"),
|
||
widget=forms.Select(attrs={"class": "form-select", 'id': 'id_member_select'})
|
||
)
|
||
can_view = forms.BooleanField(
|
||
required=False,
|
||
initial=True,
|
||
label=_("Peut consulter"),
|
||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||
)
|
||
can_edit = forms.BooleanField(
|
||
required=False,
|
||
initial=False,
|
||
label=_("Peut éditer"),
|
||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||
)
|
||
|
||
class Meta:
|
||
model = ProjectUserAccess
|
||
fields = ['user', 'can_view', 'can_edit']
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
self.fields['can_view'].initial = True
|
||
|
||
if 'user' in self.data:
|
||
try:
|
||
user_id = int(self.data.get('user'))
|
||
self.fields['user'].queryset = User.objects.filter(id=user_id)
|
||
except (ValueError, TypeError):
|
||
pass
|
||
|
||
|
||
|
||
class ProjectTeamAccessForm(forms.ModelForm):
|
||
can_view = forms.BooleanField(
|
||
required=False,
|
||
initial=True,
|
||
label=_("Peut consulter"),
|
||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||
)
|
||
can_edit = forms.BooleanField(
|
||
required=False,
|
||
initial=False,
|
||
label=_("Peut éditer"),
|
||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||
)
|
||
|
||
class Meta:
|
||
model = ProjectTeamAccess
|
||
fields = ['team', 'can_view', 'can_edit']
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
from contracts.models import CompanyTeam
|
||
self.fields['team'].queryset = CompanyTeam.objects.select_related('company').order_by('name')
|
||
self.fields['team'].widget.attrs['class'] = 'form-select'
|
||
self.fields['team'].label = _("Équipe")
|
||
self.fields['can_view'].initial = True
|
||
|
||
|
||
class ProjectForm(forms.ModelForm):
|
||
class Meta:
|
||
model = Project
|
||
fields = ['name', 'description', 'start_date', 'end_date', 'status', 'thematics', 'tags', 'pmo_number', 'osiris_number']
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
user = kwargs.pop('user', None) # on récupère l'utilisateur
|
||
self.is_update = kwargs.pop('is_update', False)
|
||
super().__init__(*args, **kwargs)
|
||
|
||
if self.is_update:
|
||
self.fields.pop('status', None) # on enlève le champ status si c'est un formulaire de modification
|
||
|
||
for field_name, field in self.fields.items():
|
||
base_class = 'form-control' if not isinstance(field.widget, forms.Select) else 'form-select'
|
||
|
||
# Ajout d’un style spécifique si le champ est facultatif
|
||
if not field.required:
|
||
style = 'border: 1px dashed #6c757d;' # gris Bootstrap
|
||
else:
|
||
style = ''
|
||
|
||
field.widget.attrs.update({
|
||
'class': base_class,
|
||
'style': style
|
||
})
|
||
|
||
if field_name == 'description':
|
||
field.widget.attrs['rows'] = 3
|
||
|
||
# Appliquer type="date" pour tous les DateInput
|
||
if isinstance(field.widget, DateInput):
|
||
# Force un format ISO pour que la valeur apparaisse dans l'input date
|
||
field.widget = DateInput(format="%Y-%m-%d", attrs=field.widget.attrs)
|
||
field.widget.input_type = 'date'
|
||
# Ajoute le format ISO aux formats acceptés pour l'input
|
||
existing_formats = list(field.input_formats or [])
|
||
if "%Y-%m-%d" not in existing_formats:
|
||
field.input_formats = ["%Y-%m-%d"] + existing_formats
|
||
|
||
# Optionnel : placeholder intelligent
|
||
# if not field.label:
|
||
# continue
|
||
# placeholder = f"{field.label} ({'optionnel' if not field.required else 'obligatoire'})"
|
||
# field.widget.attrs.setdefault('placeholder', placeholder)
|
||
|
||
|
||
# Filtrer les thématiques selon UserConfig
|
||
# if user:
|
||
# try:
|
||
# config = UserConfig.objects.get(user=user)
|
||
# if config.intervention_thematics.exists():
|
||
# self.fields['thematics'].queryset = config.intervention_thematics.all()
|
||
# except UserConfig.DoesNotExist:
|
||
# pass
|
||
|
||
|
||
|
||
|
||
class ProjectDocumentForm(forms.ModelForm):
|
||
documents = MultipleFileField(required=True, label=_("Documents"))
|
||
|
||
class Meta:
|
||
model = ProjectDocument
|
||
fields = ['documents']
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super(ProjectDocumentForm, self).__init__(*args, **kwargs)
|
||
self.fields['documents'].widget.attrs.update({'class': 'form-control'})
|
||
|
||
|
||
class ProjectOrderAssociationForm(forms.Form):
|
||
order = forms.ModelChoiceField(
|
||
queryset=None,
|
||
label=_("Commande"),
|
||
widget=forms.Select(attrs={"class": "form-select", 'id': 'id_order_select'}),
|
||
empty_label=_("Sélectionner une commande...")
|
||
)
|
||
|
||
def label_from_instance(self, obj):
|
||
"""Personnaliser l'affichage de chaque option dans le select"""
|
||
return f"{obj.order_code} - {obj.contract.contract_number} - {obj.order_date.strftime('%d/%m/%Y')}"
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
project = kwargs.pop('project', None)
|
||
user = kwargs.pop('user', None)
|
||
super().__init__(*args, **kwargs)
|
||
|
||
# Import here to avoid circular imports
|
||
from contracts.models import ContractOrder
|
||
from common.models import UserThematics
|
||
|
||
# Filter orders that are in status pending, sent ou completed
|
||
orders_queryset = ContractOrder.objects.filter(
|
||
order_status__in=['pending', 'sent', 'completed']
|
||
).select_related('contract', 'contract__company')
|
||
|
||
# Filtrer selon les permissions utilisateur
|
||
if user and project:
|
||
try:
|
||
user_config = UserConfig.objects.get(user=user)
|
||
|
||
# Récupérer les thématiques du projet
|
||
project_thematics = project.thematics.all()
|
||
|
||
# Récupérer les thématiques pour lesquelles l'utilisateur a can_edit_interventions=True
|
||
user_thematics_with_edit = UserThematics.objects.filter(
|
||
user_config=user_config,
|
||
can_edit_interventions=True
|
||
).values_list('thematic_id', flat=True)
|
||
|
||
# Vérifier s'il y a une intersection entre les thématiques du projet
|
||
# et celles où l'utilisateur peut éditer des interventions
|
||
allowed_project_thematics = project_thematics.filter(
|
||
id__in=user_thematics_with_edit
|
||
)
|
||
|
||
if not allowed_project_thematics.exists():
|
||
# Si l'utilisateur n'a pas le droit d'éditer d'interventions
|
||
# pour les thématiques du projet, on vide le queryset
|
||
orders_queryset = ContractOrder.objects.none()
|
||
else:
|
||
# Filtrer par contrats autorisés si l'utilisateur a des restrictions
|
||
if user_config.limit_interventions_to_contracts:
|
||
allowed_contracts = user_config.contracts.all()
|
||
if allowed_contracts.exists():
|
||
orders_queryset = orders_queryset.filter(contract__in=allowed_contracts)
|
||
else:
|
||
# Si limit_interventions_to_contracts est activé mais aucun contrat autorisé
|
||
orders_queryset = ContractOrder.objects.none()
|
||
|
||
except UserConfig.DoesNotExist:
|
||
# Si pas de config utilisateur, on limite l'accès
|
||
orders_queryset = ContractOrder.objects.none()
|
||
|
||
self.fields['order'].queryset = orders_queryset.order_by('-order_date')
|
||
self.fields['order'].label_from_instance = self.label_from_instance
|
||
|
||
|
||
class ProjectPhaseForm(forms.ModelForm):
|
||
class Meta:
|
||
model = ProjectPhase
|
||
fields = ['name', 'description', 'start_date', 'end_date', 'contract']
|
||
widgets = {
|
||
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||
'start_date': DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||
'end_date': DateInput(attrs={'type': 'date', 'class': 'form-control'}),
|
||
'contract': forms.Select(attrs={'class': 'form-select'}),
|
||
}
|
||
labels = {
|
||
'name': _("Nom"),
|
||
'description': _("Description"),
|
||
'start_date': _("Date de début"),
|
||
'end_date': _("Date de fin"),
|
||
'contract': _("Contrat"),
|
||
}
|
||
|
||
def __init__(self, *args, user=None, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
# Le contrat est optionnel
|
||
self.fields['contract'].required = False
|
||
self.fields['contract'].empty_label = _("Aucun contrat")
|
||
|
||
# Filtrer les contrats selon les permissions utilisateur
|
||
if user:
|
||
from contracts.permissions import get_allowed_contracts_for_user
|
||
self.fields['contract'].queryset = get_allowed_contracts_for_user(user)
|
||
|
||
# Appliquer les styles pour les champs optionnels
|
||
for field_name, field in self.fields.items():
|
||
if not field.required:
|
||
current_class = field.widget.attrs.get('class', '')
|
||
field.widget.attrs['class'] = f"{current_class} optional-field".strip() |