diff --git a/.gitignore b/.gitignore
index 311987ced..725eeece0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -106,3 +106,4 @@ labonneboite/web/static/gen/
# virtualenv
venv/
logs/
+__pycache__
\ No newline at end of file
diff --git a/docker-compose.yml b/docker-compose.yml
index 3d8551930..8dd6ba4f4 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -27,6 +27,7 @@ services:
restart: always
build:
context: .
+ dockerfile: docker/v3.6/Dockerfile
args:
options: --reload
environment:
diff --git a/docker/v3.10/Dockerfile b/docker/v3.10/Dockerfile
new file mode 100644
index 000000000..f635d3aac
--- /dev/null
+++ b/docker/v3.10/Dockerfile
@@ -0,0 +1,42 @@
+FROM python:3.10.7-slim-bullseye
+
+
+# Set timezone
+ENV TZ=Europe/Paris
+ENV FLASK_APP web.app
+
+# for mysql support & git & french langage support
+RUN apt update && apt install -y \
+ git \
+ python3-dev \
+ default-libmysqlclient-dev \
+ build-essential \
+ locales \
+ --no-install-recommends
+
+# switch working directory
+WORKDIR /app
+RUN mkdir -p /app/logs
+
+# Installing requirements
+# COPY docker/v3.6/requirements.txt /requirements.txt
+# RUN pip install -r /requirements.txt
+
+# File imports : source code
+COPY docker/v3.10/ /app/
+COPY ./labonneboite /app/labonneboite
+RUN pip install -e .
+WORKDIR /app/labonneboite
+
+# unsupported local error : https://stackoverflow.com/questions/54802935/docker-unsupported-locale-setting-when-running-python-container
+RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen \
+ && sed -i -e 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen \
+ && locale-gen
+
+# building flask assets
+RUN flask assets build
+
+# add the entrypoint
+RUN chmod +x ./run.sh
+RUN pip freeze
+ENTRYPOINT ["/bin/bash", "./run.sh"]
diff --git a/docker/v3.10/requirements.txt b/docker/v3.10/requirements.txt
new file mode 100644
index 000000000..2dec63068
--- /dev/null
+++ b/docker/v3.10/requirements.txt
@@ -0,0 +1,172 @@
+alembic==1.8.1
+arabic-reshaper==2.1.3
+asn1crypto==1.5.1
+astroid==2.12.10
+asttokens==2.0.8
+async-generator==1.10
+attrs==22.1.0
+Babel==2.10.3
+backcall==0.2.0
+blinker==1.5
+build==0.8.0
+cachetools==5.2.0
+certifi==2022.9.14
+cffi==1.15.1
+chardet==5.0.0
+charset-normalizer==2.1.1
+click==8.1.3
+cryptography==38.0.1
+cssmin==0.2.0
+cssselect2==0.7.0
+decorator==5.1.1
+defusedxml==0.7.1
+dill==0.3.5.1
+dnspython==2.2.1
+EasyProcess==1.1
+ecdsa==0.18.0
+elastic-transport==8.4.0
+elasticsearch==8.4.1
+email-validator==1.3.0
+executing==1.0.0
+first==2.0.2
+Flask==2.2.2
+Flask-Admin==1.6.0
+Flask-Assets==2.0
+Flask-BabelEx==0.9.4
+Flask-Cors==3.0.10
+Flask-DebugToolbar==0.13.1
+Flask-Login==0.6.2
+Flask-Script==2.0.6
+Flask-Testing==0.8.1
+Flask-WTF==1.0.1
+future==0.18.2
+geographiclib==1.52
+geopy==2.2.0
+gevent==21.12.0
+google-api-core==2.10.1
+google-api-python-client==2.62.0
+google-auth==2.11.1
+google-auth-httplib2==0.1.0
+google-auth-oauthlib==0.5.3
+googleapis-common-protos==1.56.4
+greenlet==1.1.3
+gunicorn==20.1.0
+h11==0.13.0
+html5lib==1.1
+httplib2==0.20.4
+idna==3.4
+importlib-metadata==4.12.0
+ipdb==0.13.9
+ipython==8.5.0
+ipython-genutils==0.2.0
+isort==5.10.1
+itsdangerous==2.1.2
+jedi==0.18.1
+Jinja2==3.1.2
+jsmin==3.0.1
+# Editable install with no version control (LaBonneBoite==0.2)
+-e /app
+labonneboite-common @ git+https://github.com/StartupsPoleEmploi/labonneboite-common.git@f0dadcbb79338522169e586a9f4ec0a750920d00
+lazy-object-proxy==1.7.1
+line-profiler==3.5.1
+lxml==4.9.1
+mailjet-rest==1.3.4
+Mako==1.2.2
+MarkupSafe==2.1.1
+matplotlib-inline==0.1.6
+mccabe==0.7.0
+msgpack-python==0.5.6
+mysqlclient==2.1.1
+nose==1.3.7
+numpy==1.23.3
+oauthlib==3.2.1
+oscrypto==1.3.0
+outcome==1.2.0
+packaging==21.3
+pandas==1.5.0
+parameterized==0.8.1
+parso==0.8.3
+pep517==0.13.0
+pexpect==4.8.0
+pickleshare==0.7.5
+Pillow==9.2.0
+pip-tools==6.8.0
+platformdirs==2.5.2
+prompt-toolkit==3.0.31
+protobuf==4.21.6
+ptyprocess==0.7.0
+pure-eval==0.2.2
+pyasn1==0.4.8
+pyasn1-modules==0.2.8
+pycparser==2.21
+pycryptodomex==3.15.0
+Pygments==2.13.0
+pyHanko==0.14.0
+pyhanko-certvalidator==0.19.5
+pyjwkest==1.4.2
+PyJWT==2.5.0
+pylint==2.15.3
+pyparsing==3.0.9
+PyPDF2==2.10.9
+PyPDF3==1.0.6
+pyprof2calltree==1.4.5
+PySocks==1.7.1
+python-bidi==0.4.2
+python-dateutil==2.8.2
+python-editor==1.0.4
+python-jose==3.3.0
+python-slugify==6.1.2
+python3-openid==3.2.0
+pytz==2022.2.1
+pytz-deprecation-shim==0.1.0.post0
+PyVirtualDisplay==3.0
+PyYAML==6.0
+qrcode==7.3.1
+raven==6.10.0
+remote-pdb==2.1.0
+reportlab==3.6.11
+requests==2.28.1
+requests-oauthlib==1.3.1
+rsa==4.9
+selenium==4.4.3
+sentry-sdk==1.9.8
+six==1.16.0
+sniffio==1.3.0
+social-auth-app-flask==1.0.0
+social-auth-app-flask-sqlalchemy==1.0.1
+social-auth-core==4.3.0
+social-auth-storage-sqlalchemy==1.1.0
+sortedcontainers==2.4.0
+speaklater==1.3
+SQLAlchemy==1.3.24
+SQLAlchemy-Utils==0.38.3
+stack-data==0.5.0
+svglib==1.4.1
+text-unidecode==1.3
+tinycss2==1.1.1
+toml==0.10.2
+tomli==2.0.1
+tomlkit==0.11.4
+tqdm==4.64.1
+traitlets==5.4.0
+trio==0.21.0
+trio-websocket==0.9.2
+typing_extensions==4.3.0
+tzdata==2022.2
+tzlocal==4.2
+Unidecode==0.4.21
+uritemplate==4.1.1
+uritools==4.0.0
+urllib3==1.26.12
+validators==0.20.0
+wcwidth==0.2.5
+webassets==2.0
+webencodings==0.5.1
+Werkzeug==2.2.2
+wrapt==1.14.1
+wsproto==1.2.0
+WTForms==3.0.1
+xhtml2pdf==0.2.8
+zipp==3.8.1
+zope.event==4.5.0
+zope.interface==5.4.0
\ No newline at end of file
diff --git a/docker/v3.10/setup.cfg b/docker/v3.10/setup.cfg
new file mode 100644
index 000000000..2ed843971
--- /dev/null
+++ b/docker/v3.10/setup.cfg
@@ -0,0 +1,94 @@
+##################################################
+# coverage #
+##################################################
+[coverage:run]
+branch = True
+parallel = true
+concurrency=multiprocessing
+
+[coverage:report]
+precision = 1
+show_missing = True
+ignore_errors = True
+exclude_lines =
+ pragma: no cover
+ raise NotImplementedError
+ def __repr__
+ if settings.DEBUG
+ if __name__ == .__main__.:
+ if TYPE_CHECKING:
+omit =
+ */test*
+ */migrations/*
+ manage.py
+ venv/*
+
+##################################################
+# flake8 #
+##################################################
+[flake8]
+ignore =
+ W503, # line break before binary operator
+ D100, # Missing docstring in public module
+ D101, # Missing docstring in public class
+ D102, # Missing docstring in public method
+ D104, # Missing docstring in public package
+ D106, # Missing docstring in public nested class
+ D200, # One-line docstring should fit on one line with quotes
+ D202, # No blank lines allowed after function docstring
+ D204, # 1 blank line required after class docstring
+ D205, # 1 blank line required between summary line and description
+ D400, # First line should end with a period
+ D406 # 1 blank line required before class docstring
+
+max-line-length = 120
+max-complexity = 18
+select = B,C,E,F,W,T4,B9
+enable-extensions = I,A,G,D
+application-import-names = api,config,post,model_utils,saas,appointment,user
+import-order-style = google
+docstring-convention = numpy
+exclude = migrations
+
+##################################################
+# isort #
+##################################################
+[isort]
+multi_line_output = 2
+line_length = 120
+order_by_type = false
+force_to_top = labonneboite.conf
+
+##################################################
+# mypy #
+##################################################
+[mypy]
+ignore_missing_imports = false
+follow_imports = silent
+# no_strict_optional = true
+show_error_codes = true
+plugins = sqlmypy
+disable_error_code = no-untyped-call
+strict = true
+
+[mypy-alembic.*,astroid.*,flask_admin.*,dateutil.*,sqlalchemy_utils.*,babel.dates.*,easyprocess.*,elasticsearch.*,elasticsearch.exceptions.*,elasticsearch.helpers.*,flask_admin.*,flask_admin.contrib.sqla.*,flask_assets.*,flask_babelex.*,flask_debugtoolbar.*,flask.ext.cors.*,flask_login.*,flask_script.*,flask_testing.*,flask_wtf.*,flask_wtf.csrf.*,geopy.*,geopy.distance.*,googleapiclient.discovery.*,google_auth_oauthlib.flow.*,google.auth.transport.requests.*,ipdb.*,locust.*,mailjet_rest.*,MySQLdb.*,numpy.*,pandas.*,parameterized.*,pyprof2calltree.*,pyvirtualdisplay.*,sklearn.*,sklearn.metrics.*,social_core.*,social_core.backends.open_id_connect.*,social_core.exceptions.*,social_flask.routes.*,social_flask_sqlalchemy.models.*,social_flask.utils.*,unidecode.*,validators.*,wtforms.*,wtforms.fields.html5.*,wtforms.validators.*,wtforms.widgets.*,xhtml2pdf.*]
+ignore_missing_imports = true
+
+##################################################
+# yapf #
+##################################################
+[yapf]
+based_on_style = google
+column_limit = 120
+split_before_logical_operator = true
+split_before_dot = true
+coalesce_brackets = true
+align_closing_bracket_with_visual_indent = true
+allow_split_before_dict_value = false
+blank_line_before_nested_class_or_def = true
+blank_lines_around_top_level_definition = 2
+
+[tool:pytest]
+python_files = test_*.py
+env =
+ LBB_ENV=test
\ No newline at end of file
diff --git a/docker/v3.10/setup.py b/docker/v3.10/setup.py
new file mode 100644
index 000000000..022feb54a
--- /dev/null
+++ b/docker/v3.10/setup.py
@@ -0,0 +1,47 @@
+import os
+from setuptools import setup
+import re
+
+
+def read(fname):
+ return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+
+pip_editable_with_egg_regex = re.compile("-e (.+#egg=(.+))")
+
+
+def requirement_to_install_require(requirement: str):
+ result = pip_editable_with_egg_regex.match(requirement)
+ if result:
+ return f"{result.group(2)} @ {result.group(1)}"
+ return requirement
+
+
+install_requires = [
+ requirement_to_install_require(req) for req in open("requirements.txt")
+]
+
+setup(
+ name="LaBonneBoite",
+ version="0.2",
+ author="La Bonne Boite",
+ author_email="labonneboite@pole-emploi.fr",
+ description=(""),
+ packages=[
+ "labonneboite",
+ ],
+ include_package_data=True,
+ install_requires=install_requires,
+ entry_points={
+ "console_scripts": [
+ "create_index = labonneboite.scripts.create_index:run",
+ ],
+ },
+ classifiers=[
+ "Development Status :: 5 - Production/Stable",
+ "License :: OSI Approved :: GNU Affero General Public License v3",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ ],
+)
diff --git a/Dockerfile b/docker/v3.6/Dockerfile
similarity index 100%
rename from Dockerfile
rename to docker/v3.6/Dockerfile
diff --git a/labonneboite/web/admin/views/office_admin_add.py b/labonneboite/web/admin/views/office_admin_add.py
index 1122da42d..9cdeade8b 100644
--- a/labonneboite/web/admin/views/office_admin_add.py
+++ b/labonneboite/web/admin/views/office_admin_add.py
@@ -10,8 +10,18 @@
from labonneboite.common.scoring import get_hirings_from_score
from labonneboite.conf import settings
from labonneboite.web.admin.forms import code_commune_validator, zip_code_validator
-from labonneboite.web.admin.forms import nospace_filter, phone_validator, strip_filter, siret_validator, naf_validator
-from labonneboite.web.admin.utils import datetime_format, AdminModelViewMixin, SelectForChoiceTypeField
+from labonneboite.web.admin.forms import (
+ nospace_filter,
+ phone_validator,
+ strip_filter,
+ siret_validator,
+ naf_validator,
+)
+from labonneboite.web.admin.utils import (
+ datetime_format,
+ AdminModelViewMixin,
+ SelectForChoiceTypeField,
+)
from labonneboite.scripts import create_index
@@ -24,187 +34,190 @@ class OfficeAdminAddModelView(AdminModelViewMixin, ModelView): # type: ignore
can_delete = False
can_edit = False
can_view_details = True
- column_searchable_list = ['siret', 'company_name', 'office_name']
- column_default_sort = ('date_created', True)
+ column_searchable_list = ["siret", "company_name", "office_name"]
+ column_default_sort = ("date_created", True)
page_size = 100
column_list = [
- 'siret',
- 'company_name',
- 'reason',
- 'date_created',
- 'date_updated',
+ "siret",
+ "company_name",
+ "reason",
+ "date_created",
+ "date_updated",
]
column_details_list = [
- 'siret',
- 'company_name',
- 'office_name',
- 'naf',
- 'street_number',
- 'street_name',
- 'zipcode',
- 'city_code',
- 'email',
- 'tel',
- 'website',
- 'flag_alternance',
- 'flag_junior',
- 'flag_senior',
- 'flag_handicap',
- 'departement',
- 'headcount',
- 'hiring',
- 'score_alternance',
- 'x',
- 'y',
- 'reason',
- 'created_by',
- 'date_created',
- 'updated_by',
- 'date_updated',
+ "siret",
+ "company_name",
+ "office_name",
+ "naf",
+ "street_number",
+ "street_name",
+ "zipcode",
+ "city_code",
+ "email",
+ "tel",
+ "website",
+ "flag_alternance",
+ "flag_junior",
+ "flag_senior",
+ "flag_handicap",
+ "departement",
+ "headcount",
+ "hiring",
+ "score_alternance",
+ "x",
+ "y",
+ "reason",
+ "created_by",
+ "date_created",
+ "updated_by",
+ "date_updated",
]
column_formatters = {
- 'date_created': datetime_format,
- 'date_updated': datetime_format,
+ "date_created": datetime_format,
+ "date_updated": datetime_format,
}
column_labels = {
- 'siret': "Siret",
- 'company_name': "Raison sociale",
- 'office_name': "Enseigne",
- 'naf': "Code NAF",
- 'street_number': "Numero rue",
- 'street_name': "Libellé rue",
- 'zipcode': "Code postal",
- 'city_code': "Code commune",
- 'email': "Email",
- 'tel': "Téléphone",
- 'website': "Site web",
- 'flag_alternance': "Drapeau alternance",
- 'flag_junior': "Drapeau junior",
- 'flag_senior': "Drapeau senior",
- 'flag_handicap': "Drapeau handicap",
- 'departement': "Département",
- 'headcount': "Tranche effectif",
- 'hiring': "hiring",
- 'score_alternance': "Score alternance",
- 'x': "Longitude",
- 'y': "Latitude",
- 'reason': "Raison",
- 'date_created': "Date de création",
- 'date_updated': "Date de modification",
- 'created_by': "Créé par",
- 'updated_by': "Modifié par",
+ "siret": "Siret",
+ "company_name": "Raison sociale",
+ "office_name": "Enseigne",
+ "naf": "Code NAF",
+ "street_number": "Numero rue",
+ "street_name": "Libellé rue",
+ "zipcode": "Code postal",
+ "city_code": "Code commune",
+ "email": "Email",
+ "tel": "Téléphone",
+ "website": "Site web",
+ "flag_alternance": "Drapeau alternance",
+ "flag_junior": "Drapeau junior",
+ "flag_senior": "Drapeau senior",
+ "flag_handicap": "Drapeau handicap",
+ "departement": "Département",
+ "headcount": "Tranche effectif",
+ "hiring": "hiring",
+ "score_alternance": "Score alternance",
+ "x": "Longitude",
+ "y": "Latitude",
+ "reason": "Raison",
+ "date_created": "Date de création",
+ "date_updated": "Date de modification",
+ "created_by": "Créé par",
+ "updated_by": "Modifié par",
}
column_descriptions = {
- 'reason': "Raison de l'ajout.",
- 'hiring': "Valeur recommandée : entre "
- f"{scoring.get_hirings_from_score(80)} et {scoring.get_hirings_from_score(90)}",
- 'score_alternance': "Valeur recommandée : entre 80 et 90",
+ "reason": "Raison de l'ajout.",
+ "hiring": "Valeur recommandée : entre "
+ f"{scoring.get_hirings_from_score(80)} et {scoring.get_hirings_from_score(90)}",
+ "score_alternance": "Valeur recommandée : entre 80 et 90",
}
form_columns = [
- 'siret',
- 'company_name',
- 'office_name',
- 'naf',
- 'street_number',
- 'street_name',
- 'zipcode',
- 'city_code',
- 'departement',
- 'email',
- 'tel',
- 'website',
- 'flag_alternance',
- 'flag_junior',
- 'flag_senior',
- 'flag_handicap',
- 'headcount',
- 'hiring',
- 'score_alternance',
- 'y',
- 'x',
- 'reason',
+ "siret",
+ "company_name",
+ "office_name",
+ "naf",
+ "street_number",
+ "street_name",
+ "zipcode",
+ "city_code",
+ "departement",
+ "email",
+ "tel",
+ "website",
+ "flag_alternance",
+ "flag_junior",
+ "flag_senior",
+ "flag_handicap",
+ "headcount",
+ "hiring",
+ "score_alternance",
+ "y",
+ "x",
+ "reason",
]
form_overrides = {
- 'headcount': SelectForChoiceTypeField,
+ "headcount": SelectForChoiceTypeField,
}
form_args = {
- 'siret': {
- 'filters': [strip_filter, nospace_filter],
- 'validators': [DataRequired(), siret_validator],
+ "siret": {
+ "filters": [strip_filter, nospace_filter],
+ "validators": [DataRequired(), siret_validator],
},
-
- 'company_name': {
- 'filters': [strip_filter],
+ "company_name": {
+ "filters": [strip_filter],
},
- 'office_name': {
- 'filters': [strip_filter],
+ "office_name": {
+ "filters": [strip_filter],
},
- 'naf': {
- 'filters': [strip_filter, nospace_filter],
- 'validators': [naf_validator]
+ "naf": {
+ "filters": [strip_filter, nospace_filter],
+ "validators": [naf_validator],
},
- 'street_number': {
- 'filters': [strip_filter, nospace_filter],
+ "street_number": {
+ "filters": [strip_filter, nospace_filter],
},
- 'street_name': {
- 'filters': [strip_filter],
+ "street_name": {
+ "filters": [strip_filter],
},
- 'zipcode': {
- 'filters': [strip_filter, nospace_filter],
- 'validators': [zip_code_validator],
+ "zipcode": {
+ "filters": [strip_filter, nospace_filter],
+ "validators": [zip_code_validator],
},
- 'city_code': {
- 'filters': [strip_filter, nospace_filter],
- 'validators': [code_commune_validator],
+ "city_code": {
+ "filters": [strip_filter, nospace_filter],
+ "validators": [code_commune_validator],
},
- 'departement': {
- 'filters': [strip_filter, nospace_filter],
+ "departement": {
+ "filters": [strip_filter, nospace_filter],
},
- 'email': {
- 'validators': [validators.optional(), validators.Email()],
+ "email": {
+ "validators": [validators.optional(), validators.Email()],
},
- 'tel': {
- 'filters': [strip_filter, nospace_filter],
- 'validators': [validators.optional(), phone_validator],
+ "tel": {
+ "filters": [strip_filter, nospace_filter],
+ "validators": [validators.optional(), phone_validator],
},
- 'website': {
- 'filters': [strip_filter],
- 'validators': [validators.optional(), validators.URL()],
+ "website": {
+ "filters": [strip_filter],
+ "validators": [validators.optional(), validators.URL()],
},
- 'headcount': {
- 'choices': settings.HEADCOUNT_INSEE_CHOICES,
+ "headcount": {
+ "choices": settings.HEADCOUNT_INSEE_CHOICES,
},
- 'hiring': {
- 'validators': [validators.NumberRange(min=0, max=get_hirings_from_score(100))],
+ "hiring": {
+ "validators": [
+ validators.NumberRange(min=0, max=get_hirings_from_score(100))
+ ],
},
- 'score_alternance': {
- 'validators': [validators.NumberRange(min=0, max=100)],
+ "score_alternance": {
+ "validators": [validators.NumberRange(min=0, max=100)],
},
- 'reason': {
- 'filters': [strip_filter],
+ "reason": {
+ "filters": [strip_filter],
},
- 'x': {
- 'filters': [strip_filter, nospace_filter],
- 'validators': [validators.required()],
+ "x": {
+ "filters": [strip_filter, nospace_filter],
+ "validators": [DataRequired()], # sytt: instead of [validators.required()]
},
- 'y': {
- 'filters': [strip_filter, nospace_filter],
- 'validators': [validators.required()],
+ "y": {
+ "filters": [strip_filter, nospace_filter],
+ "validators": [DataRequired()], # sytt: instead of [validators.required()]
},
}
- def after_model_change(self, form: BaseForm, model: OfficeAdminAdd, is_created: bool) -> None:
+ def after_model_change(
+ self, form: BaseForm, model: OfficeAdminAdd, is_created: bool
+ ) -> None:
"""
Add new office in ElacticSearch and MySQL DB and remove it from OfficeAdminRemove
if it exists in such DB
"""
- OfficeAdminRemove.query.filter_by(siret=form.data['siret']).delete()
+ OfficeAdminRemove.query.filter_by(siret=form.data["siret"]).delete()
create_index.add_individual_office(model)
diff --git a/labonneboite/web/contact_form/forms.py b/labonneboite/web/contact_form/forms.py
index f3f42825d..9ac1d38a7 100644
--- a/labonneboite/web/contact_form/forms.py
+++ b/labonneboite/web/contact_form/forms.py
@@ -1,8 +1,17 @@
from flask import request
from flask_wtf import FlaskForm
-from wtforms import BooleanField, HiddenField, RadioField, SelectMultipleField, StringField, TextAreaField
-from wtforms import validators
+from wtforms import (
+ BooleanField,
+ HiddenField,
+ RadioField,
+ SelectMultipleField,
+ StringField,
+ TextAreaField,
+)
+from wtforms import validators # sytt : this is import if it works, it's a fluke...
+
+# from wtforms.fields import EmailField, TelField # compatibility 3.10 : wtfform > 3.0.0
from wtforms.fields.html5 import EmailField, TelField
from wtforms.validators import DataRequired, Email, Optional, Regexp, URL
from wtforms.widgets import ListWidget, CheckboxInput
@@ -12,7 +21,7 @@
PHONE_REGEX = r"^(0|\+33)[1-9]([-. ]?[0-9]{2}){4}$"
-SIRET_REGEX = r'[0-9]{14}'
+SIRET_REGEX = r"[0-9]{14}"
class MultiCheckboxField(SelectMultipleField):
@@ -24,6 +33,7 @@ class MultiCheckboxField(SelectMultipleField):
https://wtforms.readthedocs.io/en/stable/specific_problems.html#specialty-field-tricks
"""
+
widget = ListWidget(prefix_label=False)
option_widget = CheckboxInput()
@@ -31,53 +41,55 @@ class MultiCheckboxField(SelectMultipleField):
class OfficeIdentificationForm(FlaskForm):
siret = StringField(
- 'N° de Siret *',
+ "N° de Siret *",
validators=[
DataRequired(),
- Regexp(SIRET_REGEX, message=("Le siret de l'établissement est invalide (14 chiffres)"))
+ Regexp(
+ SIRET_REGEX,
+ message=("Le siret de l'établissement est invalide (14 chiffres)"),
+ ),
],
- description=
- "14 chiffres, sans espace. Exemple: 36252187900034
"
- 'Retrouver mon numéro de siret ',
+ description="14 chiffres, sans espace. Exemple: 36252187900034
"
+ 'Retrouver mon numéro de siret ',
)
- last_name = StringField('Nom *', validators=[DataRequired()])
- first_name = StringField('Prénom *', validators=[DataRequired()])
+ last_name = StringField("Nom *", validators=[DataRequired()])
+ first_name = StringField("Prénom *", validators=[DataRequired()])
phone = TelField(
- 'Téléphone *',
+ "Téléphone *",
validators=[DataRequired(), Regexp(PHONE_REGEX)],
- render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"}
+ render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"},
)
email = EmailField(
- 'Adresse email *',
+ "Adresse email *",
validators=[DataRequired(), Email()],
- render_kw={"placeholder": "exemple@domaine.com"}
+ render_kw={"placeholder": "exemple@domaine.com"},
)
class OfficeHiddenIdentificationForm(FlaskForm):
- siret = HiddenField('Siret *', validators=[DataRequired()])
- last_name = HiddenField('Nom *', validators=[DataRequired()])
- first_name = HiddenField('Prénom *', validators=[DataRequired()])
+ siret = HiddenField("Siret *", validators=[DataRequired()])
+ last_name = HiddenField("Nom *", validators=[DataRequired()])
+ first_name = HiddenField("Prénom *", validators=[DataRequired()])
phone = HiddenField(
- 'Téléphone *',
+ "Téléphone *",
validators=[DataRequired(), Regexp(PHONE_REGEX)],
- render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"}
+ render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"},
)
email = HiddenField(
- 'Adresse email *',
+ "Adresse email *",
validators=[DataRequired(), Email()],
- render_kw={"placeholder": "exemple@domaine.com"}
+ render_kw={"placeholder": "exemple@domaine.com"},
)
class OfficeOtherRequestForm(OfficeHiddenIdentificationForm):
comment = TextAreaField(
- 'Votre message*',
+ "Votre message*",
validators=[DataRequired(), validators.length(max=15000)],
- description="15000 caractères maximum"
+ description="15000 caractères maximum",
)
@@ -103,8 +115,10 @@ def __init__(self, *args, **kwargs):
associated with the current office.
"""
super().__init__(*args, **kwargs)
- self.office = kwargs.pop('office')
- self.romes_choices = [(rome.code, rome.name) for rome in self.office.romes_for_naf_mapping]
+ self.office = kwargs.pop("office")
+ self.romes_choices = [
+ (rome.code, rome.name) for rome in self.office.romes_for_naf_mapping
+ ]
self.romes_to_keep.choices = self.romes_choices
self.romes_alternance_to_keep.choices = self.romes_choices
@@ -123,28 +137,34 @@ def validate(self):
# `extra_romes_to_add` and `extra_romes_alternance_to_add` are pupulated via JavaScript.
# Those fields are defined outside of the form class so we use `request.form` to get them.
extra_romes_to_add = [
- rome for rome in request.form.getlist('extra_romes_to_add')
- if rome in settings.ROME_DESCRIPTIONS and rome not in self.office.romes_codes
+ rome
+ for rome in request.form.getlist("extra_romes_to_add")
+ if rome in settings.ROME_DESCRIPTIONS
+ and rome not in self.office.romes_codes
]
extra_romes_alternance_to_add = [
- rome for rome in request.form.getlist('extra_romes_alternance_to_add')
- if rome in settings.ROME_DESCRIPTIONS and rome not in self.office.romes_codes
+ rome
+ for rome in request.form.getlist("extra_romes_alternance_to_add")
+ if rome in settings.ROME_DESCRIPTIONS
+ and rome not in self.office.romes_codes
]
# Checked ROME codes.
romes_to_keep = self.romes_to_keep.data or []
romes_to_add = set(romes_to_keep + extra_romes_to_add)
romes_alternance_to_keep = self.romes_alternance_to_keep.data or []
- romes_alternance_to_add = set(romes_alternance_to_keep + extra_romes_alternance_to_add)
+ romes_alternance_to_add = set(
+ romes_alternance_to_keep + extra_romes_alternance_to_add
+ )
# Unchecked ROME codes.
romes_to_remove = self.office.romes_codes - romes_to_add
romes_alternance_to_remove = self.office.romes_codes - romes_alternance_to_add
- setattr(self, 'romes_to_add', romes_to_add)
- setattr(self, 'romes_alternance_to_add', romes_alternance_to_add)
- setattr(self, 'romes_to_remove', romes_to_remove)
- setattr(self, 'romes_alternance_to_remove', romes_alternance_to_remove)
+ setattr(self, "romes_to_add", romes_to_add)
+ setattr(self, "romes_alternance_to_add", romes_alternance_to_add)
+ setattr(self, "romes_to_remove", romes_to_remove)
+ setattr(self, "romes_alternance_to_remove", romes_alternance_to_remove)
return True
@@ -152,57 +172,57 @@ def validate(self):
class OfficeUpdateCoordinatesForm(OfficeHiddenIdentificationForm):
CONTACT_MODES = (
- ('mail', 'Par courrier'),
- ('email', 'Par email'),
- ('phone', 'Par téléphone'),
- ('office', 'Sur place'),
- ('website', 'Via votre site internet'),
+ ("mail", "Par courrier"),
+ ("email", "Par email"),
+ ("phone", "Par téléphone"),
+ ("office", "Sur place"),
+ ("website", "Via votre site internet"),
)
CONTACT_MODES_LABELS = dict(CONTACT_MODES)
# Note : we add new_ to avoid conflict with request.args
new_contact_mode = RadioField(
- 'Mode de contact à privilégier',
- choices=CONTACT_MODES,
- default='email'
+ "Mode de contact à privilégier", choices=CONTACT_MODES, default="email"
)
new_website = StringField(
- 'Site Internet',
- validators=[URL(), Optional()], render_kw={"placeholder": "http://exemple.com"}
+ "Site Internet",
+ validators=[URL(), Optional()],
+ render_kw={"placeholder": "http://exemple.com"},
)
new_email = EmailField(
- 'Email recruteur',
- validators=[Email(), Optional()], render_kw={"placeholder": "exemple@domaine.com"}
+ "Email recruteur",
+ validators=[Email(), Optional()],
+ render_kw={"placeholder": "exemple@domaine.com"},
)
new_phone = StringField(
- 'Téléphone',
+ "Téléphone",
validators=[Optional(), Regexp(PHONE_REGEX)],
- render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"}
+ render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"},
)
- social_network = StringField('Réseau social', validators=[URL(), Optional()])
+ social_network = StringField("Réseau social", validators=[URL(), Optional()])
new_email_alternance = EmailField(
- 'Email recruteur spécialisé alternance',
+ "Email recruteur spécialisé alternance",
validators=[validators.optional(), Email()],
- render_kw={"placeholder": "exemple-alternance@domaine.com"}
+ render_kw={"placeholder": "exemple-alternance@domaine.com"},
)
new_phone_alternance = StringField(
- 'Téléphone du recruteur spécialisé alternance',
+ "Téléphone du recruteur spécialisé alternance",
validators=[validators.optional(), Regexp(PHONE_REGEX)],
- render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"}
+ render_kw={"placeholder": "01 77 86 39 49, +331 77 86 39 49"},
)
rgpd_consent = BooleanField(
- 'En cochant cette case, vous consentez à diffuser des données à caractère personnel sur les services numériques de Pôle emploi.',
- [validators.required()]
+ "En cochant cette case, vous consentez à diffuser des données à caractère personnel sur les services numériques de Pôle emploi.",
+ validators=[DataRequired()], # sytt: instead of [validators.required()]
)
class OfficeRemoveForm(OfficeHiddenIdentificationForm):
remove_lbb = BooleanField(
- 'Supprimer mon entreprise du service La Bonne Boite puisque je ne suis pas intéressé-e pour recevoir des candidatures spontanées via ce site',
- [validators.optional()]
+ "Supprimer mon entreprise du service La Bonne Boite puisque je ne suis pas intéressé-e pour recevoir des candidatures spontanées via ce site",
+ [validators.optional()],
)
remove_lba = BooleanField(
- 'Supprimer mon entreprise du service La Bonne Alternance puisque je ne suis pas intéressé-e pour recevoir des candidatures spontanées via ce site',
- [validators.optional()]
+ "Supprimer mon entreprise du service La Bonne Alternance puisque je ne suis pas intéressé-e pour recevoir des candidatures spontanées via ce site",
+ [validators.optional()],
)