Source code for translator_directory.forms.accreditation

from datetime import date

from depot.io.utils import FileIntent
from functools import cached_property
from io import BytesIO
from onegov.core.crypto import random_token
from onegov.core.utils import dictionary_to_binary
from onegov.file import File
from onegov.form import Form
from onegov.form.fields import ChosenSelectField
from onegov.form.fields import ChosenSelectMultipleField
from onegov.form.fields import PanelField
from onegov.form.fields import TagsField
from onegov.form.fields import UploadField
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import Stdnum
from onegov.form.validators import StrictOptional
from onegov.form.validators import ValidPhoneNumber
from onegov.form.validators import ValidSwissSocialSecurityNumber
from onegov.form.validators import WhitelistedMimeType
from onegov.gis import CoordinatesField
from onegov.translator_directory import _
from onegov.translator_directory.collections.language import LanguageCollection
from onegov.translator_directory.constants import GENDERS
from onegov.translator_directory.constants import INTERPRETING_TYPES
from onegov.translator_directory.constants import PROFESSIONAL_GUILDS
from onegov.translator_directory.forms.mixins import DrivingDistanceMixin
from onegov.translator_directory.models.translator import Translator
from re import match
from wtforms.fields import BooleanField
from wtforms.fields import DateField
from wtforms.fields import EmailField
from wtforms.fields import FloatField
from wtforms.fields import SelectField
from wtforms.fields import StringField
from wtforms.fields import TextAreaField
from wtforms.validators import DataRequired
from wtforms.validators import Email
from wtforms.validators import InputRequired
from wtforms.validators import Optional
from wtforms.validators import ValidationError


from typing import Any, TYPE_CHECKING

from onegov.translator_directory.utils import (nationality_choices,
                                               get_custom_text)

if TYPE_CHECKING:
    from onegov.translator_directory.models.language import Language
    from onegov.translator_directory.request import TranslatorAppRequest
    from wtforms.fields.choices import _Choice


[docs] class RequestAccreditationForm(Form, DrivingDistanceMixin):
[docs] request: 'TranslatorAppRequest'
[docs] callout = _( 'Make sure you have all information and scans of the required ' 'documents before starting to fill out the form!' )
[docs] last_name = StringField( label=_('Last name'), fieldset=_('Personal Information'), validators=[InputRequired()], )
[docs] first_name = StringField( label=_('First name'), fieldset=_('Personal Information'), validators=[InputRequired()], )
[docs] gender = ChosenSelectField( label=_('Gender'), fieldset=_('Personal Information'), choices=list(GENDERS.items()), validators=[InputRequired()], )
[docs] date_of_birth = DateField( label=_('Date of birth'), fieldset=_('Personal Information'), validators=[InputRequired()], )
[docs] hometown = StringField( label=_('Hometown'), fieldset=_('Personal Information'), validators=[InputRequired()], )
[docs] nationalities = ChosenSelectMultipleField( label=_('Nationality(ies)'), fieldset=_('Personal Information'), choices=[], # will be set in on_request validators=[InputRequired()], )
[docs] marital_status = ChosenSelectField( label=_('Marital status'), fieldset=_('Personal Information'), choices=( ('ledig', 'ledig'), ('verheiratet', 'verheiratet'), ('geschieden', 'geschieden'), ), validators=[InputRequired()], )
[docs] coordinates = CoordinatesField( label=_('Location'), description=_( 'Search for the exact address to set a marker. The address fields ' 'beneath are filled out automatically.' ), fieldset=_('Personal Information'), render_kw={'data-map-type': 'marker'}, validators=[InputRequired()], )
[docs] address = StringField( label=_('Street and house number'), fieldset=_('Personal Information'), validators=[InputRequired()], )
[docs] zip_code = StringField( label=_('Zip Code'), fieldset=_('Personal Information'), validators=[InputRequired()], )
[docs] city = StringField( label=_('City'), fieldset=_('Personal Information'), validators=[InputRequired()], )
[docs] drive_distance = FloatField( label=_('Drive distance (km)'), fieldset=_('Personal Information'), validators=[Optional()], )
[docs] withholding_tax = BooleanField( label=_('Withholding tax'), fieldset=_('Personal Information'), default=False )
[docs] self_employed = BooleanField( label=_('Self-employed'), fieldset=_('Personal Information'), default=False )
[docs] social_sec_number = StringField( label=_('Swiss social security number'), validators=[ValidSwissSocialSecurityNumber(), InputRequired()], fieldset=_('Identification / Bank details'), )
[docs] bank_name = StringField( label=_('Bank name'), fieldset=_('Identification / Bank details'), validators=[InputRequired()], )
[docs] bank_address = StringField( label=_('Bank address'), fieldset=_('Identification / Bank details'), validators=[InputRequired()], )
[docs] iban = StringField( label=_('IBAN'), fieldset=_('Identification / Bank details'), validators=[Stdnum(format='iban'), InputRequired()], )
[docs] account_owner = StringField( label=_('Account owner'), fieldset=_('Identification / Bank details'), validators=[InputRequired()], )
[docs] contact_hint = PanelField( text=_( 'Attention: It must be ensured that no one other than the ' 'applicant can take note of the information transmitted with ' 'the following contact details!' ), kind='callout', fieldset=_('Contact') )
[docs] email = EmailField( label=_('Email'), validators=[InputRequired(), Email()], fieldset=_('Contact') )
[docs] tel_private = StringField( label=_('Private Phone Number'), validators=[ValidPhoneNumber()], fieldset=_('Contact'), )
[docs] tel_office = StringField( label=_('Office Phone Number'), validators=[ValidPhoneNumber()], fieldset=_('Contact'), )
[docs] tel_mobile = StringField( label=_('Mobile Number'), validators=[ValidPhoneNumber(), InputRequired()], fieldset=_('Contact'), )
[docs] availability = SelectField( label=_('Availability'), fieldset=_('Contact'), choices=[ ('24h', '24h'), ('Vereinbarung', 'Vereinbarung') ], validators=[InputRequired()], )
[docs] confirm_name_reveal = BooleanField( label='', # will be set in on_request fieldset=_('Legal') )
[docs] profession = StringField( label=_('Learned profession'), fieldset=_('Training'), validators=[InputRequired()], )
[docs] occupation = StringField( label=_('Current professional activity'), fieldset=_('Training'), validators=[InputRequired()], )
[docs] education_as_interpreter = BooleanField( label=_('Interpreter education with degree'), fieldset=_('Training'), )
[docs] languages_hint_1 = PanelField( text=_( 'I apply for accreditation for the following working languages.' ), kind='', fieldset=_('Language training - Expertise') )
[docs] languages_hint_2 = PanelField( text=_('German C2 is a prerequisite.'), kind='callout', fieldset=_('Language training - Expertise') )
[docs] mother_tongues_ids = ChosenSelectMultipleField( label=_('Mother tongues'), fieldset=_('Language training - Expertise'), validators=[InputRequired()], choices=[], )
[docs] spoken_languages_ids = ChosenSelectMultipleField( label=_('Spoken languages'), description=_('Mention only languages with level C2.'), fieldset=_('Language training - Expertise'), validators=[StrictOptional()], choices=[] )
[docs] written_languages_ids = ChosenSelectMultipleField( label=_('Written languages'), description=_('Mention only languages with level C2.'), fieldset=_('Language training - Expertise'), validators=[StrictOptional()], choices=[] )
[docs] monitoring_languages_ids = ChosenSelectMultipleField( label=_('Monitoring languages'), description=_('Mention only languages with level C2.'), fieldset=_('Language training - Expertise'), validators=[StrictOptional()], choices=[] )
[docs] expertise_professional_guilds = ChosenSelectMultipleField( label=_('Expertise by professional guild'), fieldset=_('Qualifications'), choices=[] )
[docs] expertise_professional_guilds_other = TagsField( label=_('Expertise by professional guild: other'), description=_('Comma-separated listing'), fieldset=_('Qualifications') )
[docs] expertise_interpreting_types = ChosenSelectMultipleField( label=_('Expertise by interpreting type'), fieldset=_('Qualifications'), choices=[] )
[docs] agency_references = StringField( label=_('Authorities and courts'), description=('z.B. ZG oder Bund'), fieldset=_('References'), )
[docs] admission_course_completed = BooleanField( label=_( 'Admission course of the high court of the Canton of Zurich ' 'available' ), fieldset=_('Admission course'), default=False )
[docs] admission_course_agreement = BooleanField( label='', # will be set in on_request fieldset=_('Admission course'), default=False, depends_on=('admission_course_completed', '!y'), )
[docs] admission_hint = PanelField( text='', # will be set in on_request kind='', fieldset=_('Admission course'), depends_on=('admission_course_completed', '!y'), )
[docs] documents_hint = PanelField( text='', # will be set in on_request kind='', fieldset=_('Documents') )
[docs] declaration_of_authorization = UploadField( label=_('Signed declaration of authorization (PDF)'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] letter_of_motivation = UploadField( label=_('Short letter of motivation (PDF)'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] resume = UploadField( label=_('Resume (PDF)'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] certificates = UploadField( label=_('Certificates (PDF)'), description=_( 'For the last five years. Language proficiency certificates ' 'level C2 are mandatory for non-native speakers.' ), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] social_security_card = UploadField( label=_('Social security card (PDF)'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] passport = UploadField( label=_('Identity card, passport or foreigner identity card (PDF)'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] passport_photo = UploadField( label=_('Current passport photo (PDF)'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] debt_collection_register_extract = UploadField( label=_('Current extract from the debt collection register (PDF)'), description=_('Maximum 6 months since issue.'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] criminal_register_extract = UploadField( label=_('Current extract from the Central Criminal Register (PDF)'), description=_( 'Maximum 6 months since issue. Online order at ' 'www.strafregister.admin.ch' ), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] certificate_of_capability = UploadField( label=_('Certificate of Capability (PDF)'), description=_('Available from the municipal or city administration.'), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, fieldset=_('Documents') )
[docs] confirmation_compensation_office = UploadField( label=_( 'Confirmation from the compensation office regarding ' 'self-employment' ), validators=[ WhitelistedMimeType({'application/pdf'}), FileSizeLimit(100 * 1024 * 1024), DataRequired(), ], render_kw={'resend_upload': True}, depends_on=('self_employed', 'y'), fieldset=_('Documents') )
[docs] submission_hint = PanelField( text=_('Your data will be treated with strict confidentiality.'), kind='', fieldset=_('Submission') )
[docs] remarks = TextAreaField( label=_('Remarks'), fieldset=_('Submission') )
[docs] confirm_submission = BooleanField( label=_('By submitting, I confirm the correctness of the above data.'), fieldset=_('Submission'), default=False )
[docs] def validate_zip_code(self, field: StringField) -> None: if field.data and not match(r'\d{4}', field.data): raise ValidationError(_('Zip code must consist of 4 digits'))
[docs] def validate_email(self, field: EmailField) -> None: if field.data: field.data = field.data.lower() query = self.request.session.query translator = query(Translator).filter_by(email=field.data) if query(translator.exists()).scalar(): raise ValidationError( _('A translator with this email already exists') )
[docs] def validate_confirm_submission(self, field: BooleanField) -> None: if not field.data: raise ValidationError( _('Please confirm the correctness of the above data.') )
@cached_property
[docs] def gender_choices(self) -> list['_Choice']: return [ (id_, self.request.translate(choice)) for id_, choice in GENDERS.items() ]
@cached_property
[docs] def language_choices(self) -> list['_Choice']: languages = LanguageCollection(self.request.session) return [ (str(language.id), language.name) for language in languages.query() ]
@cached_property
[docs] def expertise_professional_guilds_choices(self) -> list['_Choice']: return [ (id_, self.request.translate(choice)) for id_, choice in PROFESSIONAL_GUILDS.items() ]
@cached_property
[docs] def expertise_interpreting_types_choices(self) -> list['_Choice']: return [ (id_, self.request.translate(choice)) for id_, choice in INTERPRETING_TYPES.items() ]
@property
[docs] def mother_tongues(self) -> list['Language']: if not self.mother_tongues_ids.data: return [] languages = LanguageCollection(self.request.session) return languages.by_ids(self.mother_tongues_ids.data)
@property
[docs] def spoken_languages(self) -> list['Language']: if not self.spoken_languages_ids.data: return [] languages = LanguageCollection(self.request.session) return languages.by_ids(self.spoken_languages_ids.data)
@property
[docs] def written_languages(self) -> list['Language']: if not self.written_languages_ids.data: return [] languages = LanguageCollection(self.request.session) return languages.by_ids(self.written_languages_ids.data)
@property
[docs] def monitoring_languages(self) -> list['Language']: if not self.monitoring_languages_ids.data: return [] languages = LanguageCollection(self.request.session) return languages.by_ids(self.monitoring_languages_ids.data)
[docs] def on_request(self) -> None: self.request.include('tags-input') self.gender.choices = self.gender_choices self.nationalities.choices = nationality_choices(self.request.locale) self.mother_tongues_ids.choices = self.language_choices self.spoken_languages_ids.choices = self.language_choices self.written_languages_ids.choices = self.language_choices self.monitoring_languages_ids.choices = self.language_choices self.expertise_professional_guilds.choices = ( self.expertise_professional_guilds_choices) self.expertise_interpreting_types.choices = ( self.expertise_interpreting_types_choices) declaration_link = self.request.app.org.meta.get('declaration_link') if declaration_link: self.declaration_of_authorization.description = declaration_link self.hide(self.drive_distance) # populate custom texts locale = self.request.locale.split('_')[0] if ( self.request.locale) else None locale = 'de' if locale == 'de' else 'en' self.admission_course_agreement.label.text = get_custom_text( self.request, f'({locale}) Custom admission course agreement' ) self.confirm_name_reveal.label.text = get_custom_text( self.request, f'({locale}) Custom confirm name reveal' ) self.admission_hint.text = get_custom_text( self.request, f'({locale}) Custom documents hint' ) self.documents_hint.text = get_custom_text( self.request, f'({locale}) Custom documents hint' )
[docs] def get_translator_data(self) -> dict[str, Any]: data = self.get_useful_data() result = { key: data.get(key) for key in ( 'last_name', 'first_name', 'gender', 'date_of_birth', 'nationalities', 'coordinates', 'address', 'zip_code', 'city', 'hometown', 'drive_distance', 'withholding_tax', 'self_employed', 'social_sec_number', 'bank_name', 'bank_address', 'iban', 'account_owner', 'email', 'tel_private', 'tel_office', 'tel_mobile', 'availability', 'confirm_name_reveal', 'education_as_interpreter', 'profession', 'occupation', 'expertise_professional_guilds', 'expertise_professional_guilds_other', 'expertise_interpreting_types', 'agency_references', ) } result['state'] = 'proposed' result['mother_tongues'] = self.mother_tongues result['spoken_languages'] = self.spoken_languages result['written_languages'] = self.written_languages result['monitoring_languages'] = self.monitoring_languages result['date_of_application'] = date.today() result['admission'] = 'uncertified' return result
[docs] def get_files(self) -> list[File]: def as_file(field: UploadField, category: str) -> File | None: name = self.request.translate(field.label.text) name = name.replace(' (PDF)', '') if field.data: return File( # type:ignore[misc] id=random_token(), name=f'{name}.pdf', note=category, reference=FileIntent( BytesIO(dictionary_to_binary( field.data # type:ignore[arg-type] )), field.data['filename'], field.data['mimetype'] ) ) return None result = [ as_file(self.declaration_of_authorization, 'Antrag'), as_file(self.letter_of_motivation, 'Antrag'), as_file(self.resume, 'Antrag'), as_file(self.certificates, 'Diplome und Zertifikate'), as_file(self.social_security_card, 'Antrag'), as_file(self.passport, 'Antrag'), as_file(self.passport_photo, 'Antrag'), as_file(self.debt_collection_register_extract, 'Abklärungen'), as_file(self.criminal_register_extract, 'Abklärungen'), as_file(self.certificate_of_capability, 'Abklärungen'), as_file(self.confirmation_compensation_office, 'Abklärungen'), ] return [r for r in result if r is not None]
[docs] def get_ticket_data(self) -> dict[str, Any]: data = self.get_useful_data() return { key: data.get(key) for key in ( 'marital_status', 'admission_course_completed', 'admission_course_agreement', 'remarks', ) }
[docs] class GrantAccreditationForm(Form):
[docs] callout = _('Create a user account and publish the translator.')
[docs] class RefuseAccreditationForm(Form):
[docs] callout = _('Delete all related data.')