Initial commit
This commit is contained in:
		
						commit
						793bb6a488
					
				
					 182 changed files with 17153 additions and 0 deletions
				
			
		
							
								
								
									
										0
									
								
								common/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								common/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										99
									
								
								common/choices.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								common/choices.py
									
										
									
									
									
										Normal 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 à l’intégration'),
 | 
			
		||||
    ('demande', 'Elaboration d’une 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 d’accueil 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
									
								
							
							
						
						
									
										26
									
								
								common/fields.py
									
										
									
									
									
										Normal 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)
 | 
			
		||||
							
								
								
									
										31
									
								
								common/formats/fr/formats.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								common/formats/fr/formats.py
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										35
									
								
								common/middleware.py
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										42
									
								
								common/urls.py
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										33
									
								
								common/wsgi.py
									
										
									
									
									
										Normal 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()
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue