loko/streetup/projects/forms.py
2026-07-22 14:48:40 +02:00

268 lines
No EOL
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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()