Initial commit

This commit is contained in:
Claude Paroz 2024-06-03 16:49:01 +02:00
commit 793bb6a488
182 changed files with 17153 additions and 0 deletions

0
common/__init__.py Normal file
View file

99
common/choices.py Normal file
View file

@ -0,0 +1,99 @@
AUTORITE_PARENTALE_CHOICES = (
('conjointe', 'Conjointe'),
('pere', 'Père'),
('mere', 'Mère'),
('tutelle', 'Tutelle')
)
DEMARCHE_CHOICES = (
('volontaire', 'Volontaire'),
('contrainte', 'Contrainte'),
('post_placement', 'Post placement'),
('non_placement', 'Eviter placement')
)
MANDATS_OPE_CHOICES = (
('volontaire', 'Mandat volontaire'),
('curatelle', 'Curatelle 308'),
('referent', 'Référent'),
('enquete', 'Enquête'),
('tutelle', 'Curatelle de portée générale'),
)
MOTIF_DEMANDE_CHOICES = (
('accompagnement', 'Accompagnement psycho-éducatif'),
('integration', 'Aide à lintégration'),
('demande', 'Elaboration dune demande (contrainte)'),
('crise', 'Travail sur la crise'),
('post-placement', 'Post-placement'),
('pre-placement', 'Pré-placement'),
('violence', 'Violence / maltraitances'),
)
MOTIFS_FIN_SUIVI_CHOICES = (
('desengagement', 'Désengagement'),
('evol_positive', 'Autonomie familiale'),
('relai_amb', 'Relai vers ambulatoire'),
('relai', 'Relai vers autre service'),
('placement', 'Placement'),
('non_aboutie', 'Demande non aboutie'),
('non_dispo', 'Pas de disponibilités/place'),
('erreur', 'Erreur de saisie'),
('autres', 'Autres'), # Obsolète (#435)
)
PROVENANCE_DESTINATION_CHOICES = (
('famille', 'Famille'),
('ies-ne', 'IES-NE'),
('ies-hc', 'IES-HC'),
('aemo', 'SAEMO'),
('fah', "Famille d'accueil"),
('refug', "Centre daccueil réfugiés"),
('hopital', "Hôpital"),
('autre', 'Autre'), # Obsolète (#435)
)
SERVICE_ORIENTEUR_CHOICES = (
('famille', 'Famille'),
('ope', 'OPE'),
('aemo', 'AEMO'),
('cnpea', 'CNPea'),
('ecole', 'École'),
('res_prim', 'Réseau primaire'),
('res_sec', 'Réseau secondaire'),
('pediatre', 'Pédiatre'),
('autre', 'Autre'),
)
TYPE_GARDE_CHOICES = (
('partage', 'garde partagée'),
('droit', 'droit de garde'),
('visite', 'droit de visite'),
)
STATUT_FINANCIER_CHOICES = (
('ai', 'AI PC'),
('gsr', 'GSR'),
('osas', 'OSAS'),
('revenu', 'Revenu'),
)
STATUT_MARITAL_CHOICES = (
('celibat', 'Célibataire'),
('mariage', 'Marié'),
('pacs', 'PACS'),
('concubin', 'Concubin'),
('veuf', 'Veuf'),
('separe', 'Séparé'),
('divorce', 'Divorcé')
)

26
common/fields.py Normal file
View file

@ -0,0 +1,26 @@
from django import forms
from django.contrib.postgres.fields import ArrayField
class ChoiceArrayField(ArrayField):
"""
From https://blogs.gnome.org/danni/2016/03/08/multiple-choice-using-djangos-postgres-arrayfield/
A field that allows us to store an array of choices.
Uses Django's postgres ArrayField and a MultipleChoiceField for its formfield.
See also https://code.djangoproject.com/ticket/27704
"""
widget = forms.CheckboxSelectMultiple
def formfield(self, **kwargs):
defaults = {
'form_class': forms.TypedMultipleChoiceField,
'coerce': self.base_field.to_python,
'choices': self.base_field.choices,
'widget': self.widget(attrs={'class': 'choicearray'}),
}
defaults.update(kwargs)
# Skip our parent's formfield implementation completely as we don't
# care for it.
# pylint:disable=bad-super-call
return super(ArrayField, self).formfield(**defaults)

View file

@ -0,0 +1,31 @@
# Mix between Django fr and de_CH formats.
# see https://docs.djangoproject.com/en/dev/ref/templates/builtins/#date
DATE_FORMAT = 'j F Y'
TIME_FORMAT = 'H:i'
DATETIME_FORMAT = 'j F Y H:i'
YEAR_MONTH_FORMAT = 'F Y'
MONTH_DAY_FORMAT = 'j F'
SHORT_DATE_FORMAT = 'j N Y'
SHORT_DATETIME_FORMAT = 'j N Y H:i'
FIRST_DAY_OF_WEEK = 1 # Monday
# The *_INPUT_FORMATS strings use the Python strftime format syntax,
# see https://docs.python.org/library/datetime.html#strftime-strptime-behavior
DATE_INPUT_FORMATS = [
'%d.%m.%Y', '%d.%m.%y', # Swiss [fr_CH), '25.10.2006', '25.10.06'
'%d/%m/%Y', '%d/%m/%y', # '25/10/2006', '25/10/06'
]
DATETIME_INPUT_FORMATS = [
'%d.%m.%Y %H:%M:%S', # '25.10.2006 14:30:59'
'%d.%m.%Y %H:%M:%S.%f', # '25.10.2006 14:30:59.000200'
'%d.%m.%Y %H:%M', # '25.10.2006 14:30'
'%d.%m.%Y', # '25.10.2006'
'%d/%m/%Y %H:%M:%S', # '25/10/2006 14:30:59'
'%d/%m/%Y %H:%M:%S.%f', # '25/10/2006 14:30:59.000200'
'%d/%m/%Y %H:%M', # '25/10/2006 14:30'
'%d/%m/%Y', # '25/10/2006'
]
DECIMAL_SEPARATOR = ','
THOUSAND_SEPARATOR = '\xa0' # non-breaking space
NUMBER_GROUPING = 3

35
common/middleware.py Normal file
View file

@ -0,0 +1,35 @@
import re
from ipaddress import ip_address
from django.conf import settings
from django.urls import reverse
from django.http import HttpResponseRedirect
EXEMPT_URLS = [
re.compile(r'^account/*'),
re.compile(r'^agenda/rendez-vous*'),
re.compile(r'^login/$'),
re.compile(r'^logout/$'),
re.compile(r'^jsi18n/$'),
re.compile(r'^favicon.ico$'),
]
class LoginRequiredMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
path = request.path_info.lstrip('/')
ip = ip_address(request.META.get('REMOTE_ADDR'))
# is_verified checks 2FA auth
if not request.user.is_verified() and not any(m.match(path) for m in EXEMPT_URLS):
ip_exempted = any(ip in net for net in settings.EXEMPT_2FA_NETWORKS)
if request.user.is_authenticated:
if ip_exempted or request.user.username in settings.EXEMPT_2FA_USERS:
return self.get_response(request)
return HttpResponseRedirect(reverse('two_factor:setup'))
else:
login_view = reverse('login') if ip_exempted else reverse('two_factor:login')
return HttpResponseRedirect("{}?next={}".format(login_view, request.path))
return self.get_response(request)

42
common/urls.py Normal file
View file

@ -0,0 +1,42 @@
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.urls import urlpatterns as auth_patterns
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import path, include
from django.views.decorators.cache import cache_page
from django.views.i18n import JavaScriptCatalog
from django.views.static import serve
from two_factor.urls import urlpatterns as tf_urls
from aemo import views
handler404 = 'aemo.views.page_not_found'
urlpatterns = [
path('', views.HomeView.as_view(), name='home'),
path('account/password_change/', views.PasswordChangeView.as_view(), name='password_change'),
# Overriden 2FA path:
path('account/two_factor/setup/', views.SetupView.as_view(), name='setup'),
path('', include(tf_urls)),
# Standard login is still permitted depending on IP origin
path('login/', LoginView.as_view(template_name='login.html'), name='login'),
path('logout/', LogoutView.as_view(next_page='/account/login'), name='logout'),
path('jsi18n/', cache_page(86400, key_prefix='jsi18n-1')(JavaScriptCatalog.as_view()),
name='javascript-catalog'),
path("", include("city_ch_autocomplete.urls")),
path('tinymce/', include('tinymce.urls')),
path('admin/', admin.site.urls),
path('', include('aemo.urls')),
path('archive/', include('archive.urls')),
path('media/<path:path>', serve,
{'document_root': settings.MEDIA_ROOT, 'show_indexes': False}),
]
# Include contrib.auth password reset URLs under the /account prefix
for pat in auth_patterns:
if pat.name.startswith('password') and pat.name != 'password_change':
pat.pattern._route = f'account/{pat.pattern._route}'
urlpatterns.append(pat)

33
common/wsgi.py Normal file
View file

@ -0,0 +1,33 @@
"""
WSGI config for aemo project.
"""
import os
from django.core.wsgi import get_wsgi_application
UPGRADING = False
def upgrade_in_progress(environ, start_response):
response_headers = [('Content-type', 'text/html')]
response = """
<body>
<h1>This site is in maintenance mode, please come back in some minutes.</h1>
<h1>Ce site est actuellement en maintenance, merci de revenir dans quelques minutes.</h1>
</body>
"""
if environ['REQUEST_METHOD'] == 'GET':
status = '200 OK'
else:
status = '403 Forbidden'
start_response(status, response_headers)
return [response.encode('utf-8')]
if UPGRADING:
application = upgrade_in_progress
else:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
application = get_wsgi_application()