from __future__ import annotations import re from django import forms from .models import SearchProfile TITLE_CHOICES = ( ("system engineer", "System engineer"), ("infrastructure engineer", "Infrastructure engineer"), ("cloud engineer", "Cloud engineer"), ("devops engineer", "DevOps engineer"), ("network engineer", "Network engineer"), ("security engineer", "Security engineer"), ("workplace engineer", "Workplace engineer"), ("support engineer", "Support engineer"), ("software engineer", "Software engineer"), ("data engineer", "Data engineer"), ("solution architect", "Solution architect"), ("project manager", "Projectmanager"), ) EXCLUDED_TITLE_CHOICES = ( ("sales", "Sales"), ("recruiter", "Recruiter"), ("account manager", "Accountmanager"), ("callcenter", "Callcenter"), ("stage", "Stage"), ("student", "Studentenjob"), ) SKILL_CHOICES = ( ("azure", "Microsoft Azure"), ("aws", "AWS"), ("microsoft 365", "Microsoft 365"), ("active directory", "Active Directory"), ("entra id", "Entra ID"), ("intune", "Microsoft Intune"), ("linux", "Linux"), ("windows server", "Windows Server"), ("networking", "Netwerken"), ("security", "Security"), ("python", "Python"), ("powershell", "PowerShell"), ("terraform", "Terraform"), ("docker", "Docker"), ("kubernetes", "Kubernetes"), ("ci/cd", "CI/CD"), ) EXCLUDED_SKILL_CHOICES = ( ("cold calling", "Cold calling"), ("door to door", "Deur-aan-deurverkoop"), ("commission only", "Alleen commissieloon"), ("night shift", "Nachtwerk"), ) EMPLOYMENT_CHOICES = ( ("full_time", "Voltijds"), ("part_time", "Deeltijds"), ("permanent", "Vast contract"), ("fixed_term", "Tijdelijk contract"), ("freelance", "Freelance"), ("internship", "Stage"), ) WORKPLACE_CHOICES = ( ("hybrid", "Hybride"), ("remote", "Volledig op afstand"), ("on_site", "Op locatie"), ) REGION_CHOICES = ( ("Antwerpen", "Antwerpen"), ("Brussels Hoofdstedelijk Gewest", "Brussel"), ("Henegouwen", "Henegouwen"), ("Limburg", "Limburg"), ("Luik", "Luik"), ("Luxemburg", "Luxemburg"), ("Namen", "Namen"), ("Oost-Vlaanderen", "Oost-Vlaanderen"), ("Vlaams-Brabant", "Vlaams-Brabant"), ("Waals-Brabant", "Waals-Brabant"), ("West-Vlaanderen", "West-Vlaanderen"), ) class SearchProfileForm(forms.ModelForm): desired_titles = forms.MultipleChoiceField( label="Gewenste functietitels", required=False, choices=TITLE_CHOICES, widget=forms.CheckboxSelectMultiple, help_text="Selecteer alle rollen die bij je zoekrichting passen.", ) excluded_titles = forms.MultipleChoiceField( label="Uitgesloten functietitels", required=False, choices=EXCLUDED_TITLE_CHOICES, widget=forms.CheckboxSelectMultiple, help_text="Vacatures met deze titelwoorden worden hard uitgesloten.", ) desired_skills = forms.MultipleChoiceField( label="Gewenste skills", required=False, choices=SKILL_CHOICES, widget=forms.CheckboxSelectMultiple, ) excluded_skills = forms.MultipleChoiceField( label="Uitgesloten kenmerken", required=False, choices=EXCLUDED_SKILL_CHOICES, widget=forms.CheckboxSelectMultiple, ) allowed_employment_types = forms.MultipleChoiceField( label="Toegestane contractvormen", required=False, choices=EMPLOYMENT_CHOICES, widget=forms.CheckboxSelectMultiple, ) preferred_workplace = forms.MultipleChoiceField( label="Voorkeurswerkmodel", required=False, choices=WORKPLACE_CHOICES, widget=forms.CheckboxSelectMultiple, ) preferred_regions = forms.MultipleChoiceField( label="Voorkeursregio's", required=False, choices=REGION_CHOICES, widget=forms.CheckboxSelectMultiple, ) excluded_regions = forms.MultipleChoiceField( label="Uitgesloten regio's", required=False, choices=REGION_CHOICES, widget=forms.CheckboxSelectMultiple, ) class Meta: model = SearchProfile fields = [ "name", "is_active", "home_postal_code", "max_distance_km", "desired_titles", "excluded_titles", "desired_skills", "excluded_skills", "allowed_employment_types", "preferred_workplace", "preferred_regions", "excluded_regions", "recommendation_threshold", "top_match_threshold", "digest_time", "learning_enabled", ] labels = { "name": "Naam", "is_active": "Actief profiel", "home_postal_code": "Thuispostcode", "max_distance_km": "Maximale afstand (km)", "recommendation_threshold": "Aanbevelingsdrempel", "top_match_threshold": "Topmatchdrempel", "digest_time": "Tijdstip dagelijkse samenvatting", "learning_enabled": "Gecontroleerd leren", } help_texts = { "home_postal_code": ( "Vier cijfers volstaan. Gemeente en coördinaten worden veilig afgeleid wanneer " "lokale geodata beschikbaar is." ), "learning_enabled": "Past alleen zachte voorkeuren aan; harde regels blijven vast.", } widgets = { "home_postal_code": forms.TextInput( attrs={"inputmode": "numeric", "pattern": "[0-9]{4}", "maxlength": 4} ), "digest_time": forms.TimeInput(attrs={"type": "time"}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields["home_postal_code"].widget.attrs["maxlength"] = 4 if not self.instance or not self.instance.pk: return for field_name in ( "desired_titles", "excluded_titles", "desired_skills", "excluded_skills", "allowed_employment_types", "preferred_workplace", "preferred_regions", "excluded_regions", ): self._include_existing_choices(field_name) def _include_existing_choices(self, field_name: str) -> None: field = self.fields[field_name] stored = getattr(self.instance, field_name, []) existing_by_casefold = { str(value).casefold(): str(value) for value, _label in field.choices } extra = [ (str(value), str(value)) for value in stored if str(value).casefold() not in existing_by_casefold ] field.choices = [*field.choices, *extra] field.initial = [ existing_by_casefold.get(str(value).casefold(), str(value)) for value in stored ] def clean_home_postal_code(self) -> str: value = str(self.cleaned_data.get("home_postal_code") or "").strip() if value and not re.fullmatch(r"\d{4}", value): raise forms.ValidationError("Gebruik een Belgische postcode van vier cijfers.") return value def clean(self): cleaned = super().clean() overlap_checks = ( ("desired_titles", "excluded_titles", "functietitels"), ("desired_skills", "excluded_skills", "skills"), ("preferred_regions", "excluded_regions", "regio's"), ) for desired_field, excluded_field, label in overlap_checks: overlap = set(cleaned.get(desired_field) or []) & set(cleaned.get(excluded_field) or []) if overlap: self.add_error( excluded_field, f"Dezelfde {label} kunnen niet tegelijk gewenst en uitgesloten zijn.", ) return cleaned