init
This commit is contained in:
19
.env
Normal file
19
.env
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
## do not put this file under version control!
|
||||||
|
##SECRET_KEY='c_r-e8v1divj8y+hu@-w=n#$xj#ciuejybd3_(k2h789(mcv8$'
|
||||||
|
DEBUG=1
|
||||||
|
|
||||||
|
SQL_DATABASE=Ogure-DB
|
||||||
|
SQL_USER=postgres
|
||||||
|
SQL_PASSWORD=postgres
|
||||||
|
SQL_HOST=db
|
||||||
|
SQL_PORT=5432
|
||||||
|
|
||||||
|
|
||||||
|
## Super-User Credentials
|
||||||
|
SUPER_USER_NAME= 'root'
|
||||||
|
SUPER_USER_PASSWORD= 'root'
|
||||||
|
SUPER_USER_EMAIL= 'admin@email.com'
|
||||||
|
SUPER_USER_ID=1
|
||||||
|
|
||||||
|
##python
|
||||||
|
PYTHONPATH="$PYTHONPATH:/usr/lib/python3.8/dist-packages:/usr/local/lib/python3.8/dist-packages
|
||||||
14
Dockerfile
Normal file
14
Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
FROM python:3.7.5-alpine
|
||||||
|
RUN pip install --upgrade pip
|
||||||
|
RUN apk add g++ gcc
|
||||||
|
COPY ./backend-django backend-django
|
||||||
|
WORKDIR /backend-django
|
||||||
|
|
||||||
|
ENV PYTHONPATH="$PYTHONPATH:/usr/lib/python3.8/dist-packages:/usr/local/lib/python3.8/dist-packages"
|
||||||
|
|
||||||
|
COPY ./requirements.txt .
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
|
||||||
|
COPY ./entrypoint.sh /
|
||||||
|
ENTRYPOINT ["sh", "/entrypoint.sh"]
|
||||||
92
README.md
Normal file
92
README.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# test_Docker
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
|
||||||
|
|
||||||
|
Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
|
||||||
|
|
||||||
|
## Add your files
|
||||||
|
|
||||||
|
- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
|
||||||
|
- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd existing_repo
|
||||||
|
git remote add origin https://scm-intradef.picsel.defense.gouv.fr/e.de-villers/test_docker.git
|
||||||
|
git branch -M master
|
||||||
|
git push -uf origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integrate with your tools
|
||||||
|
|
||||||
|
- [ ] [Set up project integrations](https://scm-intradef.picsel.defense.gouv.fr/e.de-villers/test_docker/-/settings/integrations)
|
||||||
|
|
||||||
|
## Collaborate with your team
|
||||||
|
|
||||||
|
- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
|
||||||
|
- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
|
||||||
|
- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
|
||||||
|
- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
|
||||||
|
- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
|
||||||
|
|
||||||
|
## Test and Deploy
|
||||||
|
|
||||||
|
Use the built-in continuous integration in GitLab.
|
||||||
|
|
||||||
|
- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
|
||||||
|
- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
|
||||||
|
- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
|
||||||
|
- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
|
||||||
|
- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
# Editing this README
|
||||||
|
|
||||||
|
When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template.
|
||||||
|
|
||||||
|
## Suggestions for a good README
|
||||||
|
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
|
||||||
|
|
||||||
|
## Name
|
||||||
|
Choose a self-explaining name for your project.
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
|
||||||
|
|
||||||
|
## Badges
|
||||||
|
On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
|
||||||
|
|
||||||
|
## Visuals
|
||||||
|
Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
|
||||||
|
|
||||||
|
## Support
|
||||||
|
Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
If you have ideas for releases in the future, it is a good idea to list them in the README.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
State if you are open to contributions and what your requirements are for accepting them.
|
||||||
|
|
||||||
|
For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
|
||||||
|
|
||||||
|
You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
|
||||||
|
|
||||||
|
## Authors and acknowledgment
|
||||||
|
Show your appreciation to those who have contributed to the project.
|
||||||
|
|
||||||
|
## License
|
||||||
|
For open source projects, say how it is licensed.
|
||||||
|
|
||||||
|
## Project status
|
||||||
|
If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
|
||||||
3
backend-django/.env.db
Normal file
3
backend-django/.env.db
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
POSTGRES_DB=postgres
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
17
backend-django/.env.prod
Normal file
17
backend-django/.env.prod
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
DEBUG=1
|
||||||
|
SECRET_KEY=foo
|
||||||
|
DJANGO_ALLOWED_HOSTS=*
|
||||||
|
SQL_ENGINE=django.db.backends.postgresql
|
||||||
|
SQL_DATABASE=ogure
|
||||||
|
SQL_USER=postgres
|
||||||
|
SQL_PASSWORD=postgres
|
||||||
|
SQL_HOST=127.0.0.1
|
||||||
|
SQL_PORT=5432
|
||||||
|
DATABASE=postgres
|
||||||
|
DJANGO_SUPERUSER_USERNAME="admin"
|
||||||
|
DJANGO_SUPERUSER_EMAIL="admin@ogure.app"
|
||||||
|
DJANGO_SUPERUSER_PASSWORD="admin"
|
||||||
|
DJANGO_CSRF_COOKIE_SAMESITE='Strict'
|
||||||
|
DJANGO_SESSION_COOKIE_SAMESITE='Strict'
|
||||||
|
DJANGO_CSRF_COOKIE_HTTPONLY=False
|
||||||
|
DJANGO_SESSION_COOKIE_HTTPONLY=True
|
||||||
15
backend-django/.gitignore
vendored
Normal file
15
backend-django/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/venv/
|
||||||
|
/frontend/.vscode
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
static
|
||||||
|
doc
|
||||||
|
staticfiles/
|
||||||
|
.idea/
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
__pycache__
|
||||||
|
.history
|
||||||
|
staticfiles
|
||||||
64
backend-django/README.md
Normal file
64
backend-django/README.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# backend-django
|
||||||
|
|
||||||
|
## pre-requis
|
||||||
|
|
||||||
|
- python 3.7.9
|
||||||
|
|
||||||
|
- packages pypi
|
||||||
|
création virtual_env
|
||||||
|
installation des packages depuis fichiers locaux
|
||||||
|
|
||||||
|
voir install/README.md section backend-django
|
||||||
|
|
||||||
|
- base postgres "Ogure-DB" avec "admin" ogure/ogure
|
||||||
|
|
||||||
|
voir database/README.md section postgresql
|
||||||
|
|
||||||
|
|
||||||
|
## setup virtual env "ogure_ng_venv"
|
||||||
|
|
||||||
|
````bash
|
||||||
|
cd backend-django
|
||||||
|
|
||||||
|
python3.7 -m venv ogure_ng_venv
|
||||||
|
|
||||||
|
source ogure_ng_venv/bin/activate
|
||||||
|
|
||||||
|
pip3.7 install -r requirements.txt --no-index --find-links file:///home/ogure/ogure-ng/install/pypi/backend-django
|
||||||
|
|
||||||
|
python3.7 manage.py makemigrations
|
||||||
|
pgAdmin - DB vide...
|
||||||
|
|
||||||
|
python3.7 manage.py migrate
|
||||||
|
pgAdmin - DB avec 32 tables...
|
||||||
|
|
||||||
|
python3.7 manage.py createsuperuser
|
||||||
|
admin/password2022
|
||||||
|
|
||||||
|
python3.7 manage.py collectstatic
|
||||||
|
staticfiles/
|
||||||
|
|
||||||
|
python3.7 manage.py runserver 0:8000
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
````
|
||||||
|
// deploy 'staticfiles' vers nginx
|
||||||
|
// verifier que staticfiles est bien reference dans les fichiers nginx.conf
|
||||||
|
|
||||||
|
sudo ./deploy.sh
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
test depuis poste IAG http://<HOST>:8000/admin
|
||||||
|
|
||||||
|
login avec admin/******** -> OK
|
||||||
|
|
||||||
|
création Utilisateurs
|
||||||
|
|
||||||
|
user1:
|
||||||
|
ogure/******** -> OK
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
backend-django/backend/__init__.py
Normal file
0
backend-django/backend/__init__.py
Normal file
380
backend-django/backend/admin.py
Normal file
380
backend-django/backend/admin.py
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
"""
|
||||||
|
La classe Admin est la représentation d’un modèle dans l’interface d’administration
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from django.contrib import admin, messages
|
||||||
|
from django.contrib.admin import helpers
|
||||||
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
|
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
|
||||||
|
from django.db.models import (ManyToManyField, ManyToManyRel, ManyToOneRel,
|
||||||
|
OneToOneRel)
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from backend.form import GroupeFeForm, PcpFeGroupeForm, TauxArmementForm
|
||||||
|
|
||||||
|
from .models import (FMOB, Administre, Calcul, Competence, CustomUser, Decision,
|
||||||
|
Filiere, FormationEmploi, GroupeFe, Marque, MarquesGroupe,
|
||||||
|
Notation, PcpFeGroupe, Poste, PreferencesListe,
|
||||||
|
SousVivier, SousVivierAssociation, ZoneGeographique,
|
||||||
|
RefGest, RefOrg, RefSvFil, FichiersExporte)
|
||||||
|
|
||||||
|
|
||||||
|
def is_not_many_to(field):
|
||||||
|
""" Vérifie qu'il ne s'agit ni d'un champ many-to-one ni d'un champ many-to-many """
|
||||||
|
return not isinstance(field, ManyToManyField) and not isinstance(field, ManyToManyRel) and (not isinstance(field, ManyToOneRel) or isinstance(field, OneToOneRel))
|
||||||
|
|
||||||
|
def default_list_display(modelType):
|
||||||
|
""" Retourne une liste de champs pour l'attribut list_display """
|
||||||
|
pk = modelType._meta.pk.name
|
||||||
|
fields = [field.name for field in modelType._meta.get_fields() if field.name != pk and is_not_many_to(field)]
|
||||||
|
fields.insert(0, pk)
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
class AdministreAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des administrés dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs des administrés qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(Administre)
|
||||||
|
|
||||||
|
|
||||||
|
class CalculAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des administrés dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs des administrés qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(Calcul)
|
||||||
|
|
||||||
|
|
||||||
|
class DecisionAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des décisions dans la page d'admin du projet."""
|
||||||
|
|
||||||
|
list_display = default_list_display(Decision)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.list_display_links = None
|
||||||
|
|
||||||
|
def has_add_permission(self, request):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class NotationAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section notation dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de notation qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(Notation)
|
||||||
|
|
||||||
|
|
||||||
|
class PosteAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des postes dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs des postes qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(Poste)
|
||||||
|
|
||||||
|
class ZoneGeographiqueAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des postes dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs des postes qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(ZoneGeographique)
|
||||||
|
|
||||||
|
|
||||||
|
class PreferencesListeAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de preferences liste dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de preferences liste qui doivent être affichés dans cette section dans la page admin
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(PreferencesListe)
|
||||||
|
|
||||||
|
|
||||||
|
class FMOBAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de FMOB dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de FMOB qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(FMOB)
|
||||||
|
|
||||||
|
|
||||||
|
class MarqueAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de Marque dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de la talbe Marque qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(Marque)
|
||||||
|
|
||||||
|
|
||||||
|
class FiliereAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de Filiere dans la page d'admin du projet.
|
||||||
|
list_display va spécifier les champs f_code et domaine de la talbe Filiere qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(Filiere)
|
||||||
|
|
||||||
|
|
||||||
|
class SousVivierAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de sous vivier dans la page d'admin du projet.
|
||||||
|
list_display va spécifier les champs sv_id, gestionnaire et sv_libelle de la talbe sous vivier qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(SousVivier)
|
||||||
|
|
||||||
|
|
||||||
|
class RefGestAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de référentiels gestionnaires dans la page d'admin du projet.
|
||||||
|
list_display va spécifier les champs de la talbe référentiel gestionnaire qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(RefGest)
|
||||||
|
|
||||||
|
|
||||||
|
class RefOrgAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de référentiels organiques dans la page d'admin du projet.
|
||||||
|
list_display va spécifier les champs de la talbe referentiel organique qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(RefOrg)
|
||||||
|
|
||||||
|
|
||||||
|
class RefSvFilAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de référentiels sous-vivier filiere dans la page d'admin du projet.
|
||||||
|
list_display va spécifier les champs de la talbe referentiel sous-vivier filiere qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(RefSvFil)
|
||||||
|
|
||||||
|
|
||||||
|
class FichiersExporteAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des fichiers exportés dans la page d'admin du projet.
|
||||||
|
list_display va spécifier les champs id, nom_fichier et date_export de la talbe des fichiers exportés qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(FichiersExporte)
|
||||||
|
|
||||||
|
|
||||||
|
# class SousVivierAssociationAdmin(admin.ModelAdmin):
|
||||||
|
# """Cette classe est dédiée à la section de SousVivierAssociation dans la page d'admin du projet.
|
||||||
|
# list_display va spécifier tous les champs de la talbe SousVivierAssociation qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
# """
|
||||||
|
# list_display = default_list_display(SousVivierAssociation)
|
||||||
|
|
||||||
|
|
||||||
|
class MarquesGroupeAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section de MarquesGroupe dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de la talbe MarquesGroupe qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(MarquesGroupe)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserCreationForm(UserCreationForm):
|
||||||
|
"""Cette classe est dédiée à la section de creation d'un utilisateur dans la page d'admin du projet.
|
||||||
|
|
||||||
|
"""
|
||||||
|
class Meta(UserCreationForm.Meta):
|
||||||
|
model = CustomUser
|
||||||
|
fields = UserCreationForm.Meta.fields + ('id', 'grade')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserChangeForm(UserChangeForm):
|
||||||
|
"""Cette classe est dédiée à la section de changement d'un utilisateur dans la page d'admin du projet.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta(UserChangeForm.Meta):
|
||||||
|
model = CustomUser
|
||||||
|
fields = UserCreationForm.Meta.fields + ('id', 'grade')
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserAdmin(BaseUserAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des utilisateurs dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de la talbe CustomUser qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
model = CustomUser
|
||||||
|
form = CustomUserChangeForm
|
||||||
|
add_form = CustomUserCreationForm
|
||||||
|
list_display = BaseUserAdmin.list_display + ('id', 'grade')
|
||||||
|
fieldsets = BaseUserAdmin.fieldsets + (
|
||||||
|
('Gestionnaire', {
|
||||||
|
'fields': ('id', 'grade'),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
add_fieldsets = BaseUserAdmin.add_fieldsets + (
|
||||||
|
('Gestionnaire', {
|
||||||
|
'fields': ('id', 'grade'),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
# list_display = [field.name for field in CustomUser._meta.get_fields() if not isinstance(field, ManyToManyRel)
|
||||||
|
# and not field.name in ['logentry', 'sousvivier']]
|
||||||
|
|
||||||
|
|
||||||
|
class CompetenceAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section des competences dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de la talbe Competence qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = default_list_display(Competence)
|
||||||
|
|
||||||
|
|
||||||
|
class FormationEmploiAdmin(admin.ModelAdmin):
|
||||||
|
"""Cette classe est dédiée à la section Formation Emploi dans la page d'admin du projet.
|
||||||
|
list_display va spécifier tous les champs de la talbe FE qui doivent être affichés dans cette section dans la page admin.
|
||||||
|
|
||||||
|
"""
|
||||||
|
list_display = ['fe_code', 'fe_libelle', 'fe_garnison_lieu', 'fe_code_postal', 'groupe_fe', 'fe_taux_armement_cible_mdr',
|
||||||
|
'fe_taux_armement_cible_off', 'fe_taux_armement_cible_soff', 'get_mere', 'zone_defense']
|
||||||
|
list_filter = ('groupe_fe__groupe_fe_nom', 'fe_garnison_lieu')
|
||||||
|
actions = ['modification_taux_armement_mdr', 'modification_taux_armement_off', 'modification_taux_armement_soff', 'attribution_groupe']
|
||||||
|
search_fields = ['fe_code']
|
||||||
|
|
||||||
|
@admin.display(description='FE mère')
|
||||||
|
def get_mere(self, obj):
|
||||||
|
return obj.mere.pk if obj.mere else None
|
||||||
|
|
||||||
|
# Fonctions d'actions supplémentaires
|
||||||
|
@admin.action(description="Changer le taux d'armement pour la catégorie MDR")
|
||||||
|
def modification_taux_armement_mdr(self, request, queryset):
|
||||||
|
"""Fonction dédiée à la modification du taux d'armement mdr dans la section Formation Emploi de la page d'administration.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if 'apply' in request.POST:
|
||||||
|
taux = int(request.POST['taux_armement_cible'])
|
||||||
|
for fe_id in queryset.values_list('fe_code'):
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_id[0])
|
||||||
|
fe.fe_taux_armement_cible_mdr = taux
|
||||||
|
new_nb_reevalue_mdr = np.ceil(0.01 * taux * fe.fe_nb_poste_reo_mdr)
|
||||||
|
new_nb_vacant_mdr = new_nb_reevalue_mdr - fe.fe_nb_poste_occupe_mdr
|
||||||
|
fe.fe_nb_poste_reevalue_mdr = new_nb_reevalue_mdr
|
||||||
|
fe.fe_nb_poste_vacant_mdr = new_nb_vacant_mdr
|
||||||
|
fe.save(update_fields=['fe_taux_armement_cible_mdr', 'fe_nb_poste_reevalue_mdr', 'fe_nb_poste_vacant_mdr'])
|
||||||
|
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||||
|
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
|
||||||
|
form = TauxArmementForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'admin/taux_cible_mdr.html',
|
||||||
|
{'form': form, 'formations_emplois': queryset, 'do_action': 'modification_taux_armement_mdr', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||||
|
|
||||||
|
modification_taux_armement_mdr.short_description = "Modification du taux d'armement cible pour la categorie MDR"
|
||||||
|
|
||||||
|
@admin.action(description="Changer le taux d'armement pour la catégorie OFF")
|
||||||
|
def modification_taux_armement_off(self, request, queryset):
|
||||||
|
"""Fonction dédiée à la modification du taux d'armement off dans la section Formation Emploi de la page d'administration.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if 'apply' in request.POST:
|
||||||
|
taux = int(request.POST['taux_armement_cible'])
|
||||||
|
for fe_id in queryset.values_list('fe_code'):
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_id[0])
|
||||||
|
fe.fe_taux_armement_cible_off = taux
|
||||||
|
new_nb_reevalue_off = np.ceil(0.01 * taux * fe.fe_nb_poste_reo_off)
|
||||||
|
new_nb_vacant_off = new_nb_reevalue_off - fe.fe_nb_poste_occupe_off
|
||||||
|
fe.fe_nb_poste_reevalue_off = new_nb_reevalue_off
|
||||||
|
fe.fe_nb_poste_vacant_off = new_nb_vacant_off
|
||||||
|
fe.save(update_fields=['fe_taux_armement_cible_off', 'fe_nb_poste_reevalue_off', 'fe_nb_poste_vacant_off'])
|
||||||
|
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||||
|
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
|
||||||
|
form = TauxArmementForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'admin/taux_cible_off.html',
|
||||||
|
{'form': form, 'formations_emplois': queryset, 'do_action': 'modification_taux_armement_off', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||||
|
|
||||||
|
modification_taux_armement_off.short_description = "Modification du taux d'armement cible pour la categorie OFF"
|
||||||
|
|
||||||
|
@admin.action(description="Changer le taux d'armement pour la catégorie SOFF")
|
||||||
|
def modification_taux_armement_soff(self, request, queryset):
|
||||||
|
"""Fonction dédiée à la modification du taux d'armement soff dans la section Formation Emploi de la page d'administration.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if 'apply' in request.POST:
|
||||||
|
taux = int(request.POST['taux_armement_cible'])
|
||||||
|
for fe_id in queryset.values_list('fe_code'):
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_id[0])
|
||||||
|
fe.fe_taux_armement_cible_soff = taux
|
||||||
|
new_nb_reevalue_soff = np.ceil(0.01 * taux * fe.fe_nb_poste_reo_soff)
|
||||||
|
new_nb_vacant_soff = new_nb_reevalue_soff - fe.fe_nb_poste_occupe_soff
|
||||||
|
fe.fe_nb_poste_reevalue_soff = new_nb_reevalue_soff
|
||||||
|
fe.fe_nb_poste_vacant_soff = new_nb_vacant_soff
|
||||||
|
fe.save(update_fields=['fe_taux_armement_cible_soff', 'fe_nb_poste_reevalue_soff', 'fe_nb_poste_vacant_soff'])
|
||||||
|
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||||
|
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
|
||||||
|
form = TauxArmementForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'admin/taux_cible_soff.html',
|
||||||
|
{'form': form, 'formations_emplois': queryset, 'do_action': 'modification_taux_armement_soff', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||||
|
|
||||||
|
modification_taux_armement_soff.short_description = "Modification du taux d'armement cible pour la categorie SOFF"
|
||||||
|
|
||||||
|
@admin.action(description='Attribuer les FE à un groupe')
|
||||||
|
def attribution_groupe(modeladmin, request, queryset):
|
||||||
|
"""Fonction dédiée à l'attribution du group pour la formation emploi dans la section Formation Emploi de la page d'administration.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if 'apply' in request.POST:
|
||||||
|
nom = request.POST['nom_groupe_fe']
|
||||||
|
try:
|
||||||
|
groupe_fe = GroupeFe.objects.get(groupe_fe_nom=nom)
|
||||||
|
except:
|
||||||
|
groupe_fe = GroupeFe(groupe_fe_nom=nom)
|
||||||
|
groupe_fe.save()
|
||||||
|
|
||||||
|
fe = FormationEmploi.objects.filter(fe_code__in=queryset.values_list('fe_code'))
|
||||||
|
fe.update(groupe_fe_id=groupe_fe.pk)
|
||||||
|
messages.success(request, '{0} FEs ont été mises à jour'.format(queryset.count()))
|
||||||
|
|
||||||
|
return HttpResponseRedirect(request.get_full_path())
|
||||||
|
|
||||||
|
form = GroupeFeForm(initial={'_selected_action': queryset.values_list('fe_code', flat=True)})
|
||||||
|
|
||||||
|
return render(request,
|
||||||
|
'admin/groupe_fe.html',
|
||||||
|
{'form': form, 'formations_emplois': queryset, 'do_action': 'attribution_groupe', 'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME})
|
||||||
|
|
||||||
|
attribution_groupe.short_description = "Attribution des FE à un groupe"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# class PcpFeGroupeAdmin(admin.ModelAdmin):
|
||||||
|
# """Cette classe est dédiée à l'association des groupes de FE aux gestionnaires PCP dans la page d'admin du projet.
|
||||||
|
|
||||||
|
# """
|
||||||
|
# list_display = default_list_display(PcpFeGroupe)
|
||||||
|
# form = PcpFeGroupeForm
|
||||||
|
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
|
||||||
|
admin.site.register(Administre, AdministreAdmin)
|
||||||
|
admin.site.register(Calcul, CalculAdmin)
|
||||||
|
admin.site.register(Decision, DecisionAdmin)
|
||||||
|
admin.site.register(FichiersExporte, FichiersExporteAdmin)
|
||||||
|
admin.site.register(Poste, PosteAdmin)
|
||||||
|
admin.site.register(SousVivier, SousVivierAdmin)
|
||||||
|
admin.site.register(RefGest, RefGestAdmin)
|
||||||
|
admin.site.register(RefOrg, RefOrgAdmin)
|
||||||
|
admin.site.register(RefSvFil, RefSvFilAdmin)
|
||||||
|
admin.site.register(Notation, NotationAdmin)
|
||||||
|
admin.site.register(Filiere, FiliereAdmin)
|
||||||
|
admin.site.register(FormationEmploi, FormationEmploiAdmin)
|
||||||
|
# admin.site.register(SousVivierAssociation, SousVivierAssociationAdmin)
|
||||||
|
admin.site.register(FMOB, FMOBAdmin)
|
||||||
|
admin.site.register(Marque, MarqueAdmin)
|
||||||
|
admin.site.register(MarquesGroupe, MarquesGroupeAdmin)
|
||||||
|
admin.site.register(CustomUser, CustomUserAdmin)
|
||||||
|
admin.site.register(Competence, CompetenceAdmin)
|
||||||
|
# admin.site.register(PcpFeGroupe, PcpFeGroupeAdmin)
|
||||||
|
admin.site.register(ZoneGeographique, ZoneGeographiqueAdmin)
|
||||||
13
backend-django/backend/apps.py
Normal file
13
backend-django/backend/apps.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"""
|
||||||
|
Ce fichier est créé pour inclure toute configuration de l'application.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BackendConfig(AppConfig):
|
||||||
|
"""Cette classe permet de configurer les variables du backend.
|
||||||
|
|
||||||
|
"""
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'backend'
|
||||||
7
backend-django/backend/constants.py
Normal file
7
backend-django/backend/constants.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""Ce fichier contient les constantes utilisées dans le backend.
|
||||||
|
"""
|
||||||
|
CATEGORIE_MDR = "MDR"
|
||||||
|
CATEGORIE_SOFF = "SOFF"
|
||||||
|
CATEGORIE_OFF = "OFF"
|
||||||
|
CATEGORIE_BY_NF = {'1A': 'MDR', '1B': 'MDR', '1C': 'MDR', '2.': 'SOFF', '3A': 'SOFF', '3B': 'SOFF', '3B NFS': 'SOFF', '4.': 'OFF',
|
||||||
|
'5A': 'OFF', '5B': 'OFF', '5C': 'OFF', '6A': 'OGX', '6B': 'OGX'}
|
||||||
414
backend-django/backend/filters.py
Normal file
414
backend-django/backend/filters.py
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
"""
|
||||||
|
Ce fichier contient des fonctions qui permet aux utilisateurs de filtrer un ensemble de questions sur la base des champs d'un modèle.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.models import Q
|
||||||
|
from django_filters import rest_framework as filters
|
||||||
|
from requests import request
|
||||||
|
from rest_framework.filters import OrderingFilter
|
||||||
|
|
||||||
|
|
||||||
|
from .models import Administre
|
||||||
|
from .models import AvisPosteChoices as AvisPoste
|
||||||
|
from .models import StatutPamChoices as StatutPam
|
||||||
|
|
||||||
|
from .models import (Decision, FormationEmploi, Poste, RefGest, RefOrg,
|
||||||
|
RefSvFil, SpecifiqueChoices, Administres_Pams,
|
||||||
|
Postes_Pams)
|
||||||
|
from .utils.decorators import class_logger
|
||||||
|
from .utils.logging import get_logger
|
||||||
|
from .utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||||
|
get_adm_filter_by_lvl4_codes_fil,
|
||||||
|
get_adm_filter_by_lvl4_codes_future_pcp,
|
||||||
|
get_adm_filter_by_lvl4_codes_pcp,
|
||||||
|
get_lvl4_org_codes_by_any_code,
|
||||||
|
get_poste_filter_by_lvl4_codes_fil,
|
||||||
|
get_poste_filter_by_lvl4_codes_pcp,
|
||||||
|
get_profile_summary, get_queryset_org_by_user,
|
||||||
|
is_truthy)
|
||||||
|
|
||||||
|
# paramètre de requête pour restreindre par formation(s) d'emploi(s)
|
||||||
|
REQ_PARAM_FE = 'formation_emploi'
|
||||||
|
|
||||||
|
# paramètre de requête pour restreindre à un seul profil
|
||||||
|
REQ_PARAM_ONLY_PROFILE = 'profile'
|
||||||
|
|
||||||
|
# [poste] paramètre de requête pour restreindre par p_specifique
|
||||||
|
REQ_PARAM_POSTE_SPECIFIQUE = 'p_specifique'
|
||||||
|
|
||||||
|
# paramètre de requête pour restreindre par sous-vivier
|
||||||
|
REQ_PARAM_SOUS_VIVIER = 'sous_vivier'
|
||||||
|
|
||||||
|
@class_logger
|
||||||
|
class AdministreFilter(filters.FilterSet):
|
||||||
|
"""
|
||||||
|
Cette classe contient tous les filtres qui peuvent être effectués sur la table des administrés.
|
||||||
|
"""
|
||||||
|
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||||
|
formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
|
||||||
|
sous_vivier__in = filters.CharFilter(method='sous_vivier_filter')
|
||||||
|
pam__in = filters.CharFilter(method='pam_filter')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Administre
|
||||||
|
fields = {
|
||||||
|
Administre.Cols.REL_SOUS_VIVIER: ['exact'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||||
|
statut = [StatutPam.A_ETUDIER, StatutPam.A_MUTER]
|
||||||
|
return queryset.filter(Q(**{f'{Administre.Cols.REL_PAM_INTER}__a_statut_pam_annee__in' : statut})
|
||||||
|
& Q(**{f'{Administre.Cols.REL_PAM_INTER}__{Administres_Pams.Cols.REL_DECISION}__de_decision__isnull' :True})
|
||||||
|
& Q(**{f'{Administre.Cols.REL_PAM_INTER}__pam_id__exact' : value}))
|
||||||
|
|
||||||
|
def formation_emploi_filter(self, queryset, name, value):
|
||||||
|
list_values = value.split(',')
|
||||||
|
return queryset.filter(Q(**{f'{Administre.Cols.REL_FORMATION_EMPLOI}__fe_code__in': list_values}) \
|
||||||
|
| Q(**{f'{Administre.Cols.REL_PAM_INTER}__{Administres_Pams.Cols.REL_DECISION}__{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}__fe_code__in': list_values}))
|
||||||
|
|
||||||
|
|
||||||
|
def pam_filter(self, queryset, name, value):
|
||||||
|
return queryset.filter(Q(**{f'{Administre.Cols.M2M_PAM}__pam_id__icontains': value}))
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def qs(self):
|
||||||
|
qs = super().qs
|
||||||
|
|
||||||
|
|
||||||
|
summary = get_profile_summary(self.request.user)
|
||||||
|
|
||||||
|
profiles = summary.profiles.get(KEY_READ, ())
|
||||||
|
if not profiles:
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||||
|
|
||||||
|
org_code = summary.org_code
|
||||||
|
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||||
|
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||||
|
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||||
|
if not org_code or (not is_fil and not is_pcp and not is_bvt):
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||||
|
adm_filter = None
|
||||||
|
if is_fil:
|
||||||
|
adm_filter = get_adm_filter_by_lvl4_codes_fil(codes_lvl4)
|
||||||
|
if is_pcp:
|
||||||
|
pcp_filter = get_adm_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||||
|
| get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
|
||||||
|
adm_filter = adm_filter | pcp_filter if adm_filter else pcp_filter
|
||||||
|
if is_bvt:
|
||||||
|
bvt_filter = Q(**{f'{Administre.Cols.REL_SOUS_VIVIER}': 'BVT'})
|
||||||
|
adm_filter = adm_filter | bvt_filter if adm_filter else bvt_filter
|
||||||
|
|
||||||
|
return qs.filter(adm_filter)
|
||||||
|
|
||||||
|
|
||||||
|
@class_logger
|
||||||
|
class AdministrePAMFilter(filters.FilterSet):
|
||||||
|
"""
|
||||||
|
Cette classe contient tous les filtres qui peuvent être effectués sur la table des administrés.
|
||||||
|
"""
|
||||||
|
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||||
|
formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
|
||||||
|
pam__in = filters.CharFilter(method='pam_filter')
|
||||||
|
sous_vivier = filters.CharFilter(method='sous_vivier_filter')
|
||||||
|
adm__in = filters.CharFilter(method='administre_filter')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Administres_Pams
|
||||||
|
fields = {
|
||||||
|
}
|
||||||
|
|
||||||
|
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||||
|
statut = [StatutPam.A_ETUDIER, StatutPam.A_MUTER]
|
||||||
|
return queryset.filter(Q(a_statut_pam_annee__in = statut)
|
||||||
|
& Q(**{f'{Administres_Pams.Cols.REL_DECISION}__de_decision__isnull' :True}))
|
||||||
|
|
||||||
|
def administre_filter(self,queryset, name, value):
|
||||||
|
values_list = value.split(',')
|
||||||
|
if values_list:
|
||||||
|
return queryset.filter(id__in=values_list)
|
||||||
|
|
||||||
|
def formation_emploi_filter(self, queryset, name, value):
|
||||||
|
list_values = value.split(',')
|
||||||
|
return queryset.filter(Q(**{f'{Administres_Pams.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_FORMATION_EMPLOI}__fe_mere_credo__in': list_values}) \
|
||||||
|
| Q(**{f'{Administres_Pams.Cols.REL_DECISION}__{Decision.Cols.REL_POSTE}__{Poste.Cols.REL_FORMATION_EMPLOI}__fe_mere_credo__in': list_values}))
|
||||||
|
|
||||||
|
def pam_filter(self, queryset, name, value):
|
||||||
|
return queryset.filter(pam_id = value)
|
||||||
|
|
||||||
|
def sous_vivier_filter(self, queryset, name, value):
|
||||||
|
list_values = value.split('-')
|
||||||
|
return queryset.filter(administre__sous_vivier__in = list_values)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def qs(self):
|
||||||
|
qs = super().qs
|
||||||
|
summary = get_profile_summary(self.request.user)
|
||||||
|
|
||||||
|
profiles = summary.profiles.get(KEY_READ, ())
|
||||||
|
if not profiles:
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||||
|
|
||||||
|
org_code = summary.org_code
|
||||||
|
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||||
|
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||||
|
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||||
|
if not org_code or (not is_fil and not is_pcp and not is_bvt):
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||||
|
adm_filter = None
|
||||||
|
if is_fil:
|
||||||
|
adm_filter = get_adm_filter_by_lvl4_codes_fil(codes_lvl4)
|
||||||
|
if is_pcp:
|
||||||
|
pcp_filter = get_adm_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||||
|
| get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
|
||||||
|
adm_filter = adm_filter | pcp_filter if adm_filter else pcp_filter
|
||||||
|
if is_bvt:
|
||||||
|
bvt_filter = Q(**{f'{Administres_Pams.Cols.REL_ADMINISTRE}__{Administre.Cols.REL_SOUS_VIVIER}': 'BVT'})
|
||||||
|
adm_filter = adm_filter | bvt_filter if adm_filter else bvt_filter
|
||||||
|
|
||||||
|
return qs.filter(adm_filter)
|
||||||
|
|
||||||
|
class DecisionFilter(filters.FilterSet):
|
||||||
|
"""
|
||||||
|
Cette classe contient tous les filtres qui peuvent être effectués sur la table des decisions.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = ['administre_id', 'poste_id']
|
||||||
|
|
||||||
|
|
||||||
|
@class_logger
|
||||||
|
class PosteFilter(filters.FilterSet):
|
||||||
|
"""
|
||||||
|
Cette classe contient tous les filtres qui peuvent être effectués sur la table des postes.
|
||||||
|
"""
|
||||||
|
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||||
|
pam__in = filters.CharFilter(method='pam_filter')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Poste
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
'p_specifique',
|
||||||
|
'p_itd_affecte',
|
||||||
|
Poste.Cols.REL_FORMATION_EMPLOI
|
||||||
|
]
|
||||||
|
|
||||||
|
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||||
|
statut = [AvisPoste.P1, AvisPoste.P2, AvisPoste.P3, AvisPoste.P4]
|
||||||
|
return queryset.filter(Q(p_avis__in=statut) & Q(poste__decisions__isnull=True) & Q(poste__p_pam_id__exact = value))
|
||||||
|
|
||||||
|
def pam_filter(self, queryset, name, value):
|
||||||
|
values_list = value.split(',')
|
||||||
|
return queryset.filter(Q(**{f'{Poste.Cols.M2M_PAM}__pam_id__in': values_list}))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def qs(self):
|
||||||
|
qs = super().qs
|
||||||
|
|
||||||
|
summary = get_profile_summary(self.request.user)
|
||||||
|
|
||||||
|
profiles = summary.profiles.get(KEY_READ, ())
|
||||||
|
if not profiles:
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||||
|
|
||||||
|
org_code = summary.org_code
|
||||||
|
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||||
|
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||||
|
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||||
|
is_itd = Profiles.ITD in profiles and (not only_profile or only_profile == Profiles.ITD)
|
||||||
|
if not org_code or (not is_fil and not is_pcp and not is_bvt and not is_itd):
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||||
|
|
||||||
|
poste_filter = None
|
||||||
|
if is_fil:
|
||||||
|
poste_filter = get_poste_filter_by_lvl4_codes_fil(codes_lvl4) \
|
||||||
|
| Q(p_specifique=SpecifiqueChoices.ITD)
|
||||||
|
if is_pcp:
|
||||||
|
pcp_filter = get_poste_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||||
|
| Q(p_specifique=SpecifiqueChoices.ITD)
|
||||||
|
poste_filter = poste_filter | pcp_filter if poste_filter else pcp_filter
|
||||||
|
if is_itd:
|
||||||
|
itd_filter = Q(p_specifique=SpecifiqueChoices.ITD)
|
||||||
|
poste_filter = poste_filter | itd_filter if poste_filter else itd_filter
|
||||||
|
if is_bvt:
|
||||||
|
bvt_filter = Q(**{f'{Poste.Cols.M2M_SOUS_VIVIERS}': 'BVT'})
|
||||||
|
poste_filter = poste_filter | bvt_filter if poste_filter else bvt_filter
|
||||||
|
|
||||||
|
return qs.filter(poste_filter)
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_fields(cls):
|
||||||
|
"""
|
||||||
|
Cette fonction renvoie toutes les valeurs de recherche dans
|
||||||
|
les champs qui peuvent être filtrés. Les champs qui peuvent être filtrés sont spécifiés dans la classe Meta variable fields.
|
||||||
|
"""
|
||||||
|
fields = super().get_fields()
|
||||||
|
for field_name in fields.copy():
|
||||||
|
lookup_list = cls.Meta.model._meta.get_field(field_name).get_lookups().keys()
|
||||||
|
fields[field_name] = lookup_list
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@class_logger
|
||||||
|
class PostePAMFilter(filters.FilterSet):
|
||||||
|
"""
|
||||||
|
Cette classe contient tous les filtres qui peuvent être effectués sur la table des postes.
|
||||||
|
"""
|
||||||
|
aide_a_la_decision = filters.CharFilter(method='aide_a_la_decision_filter')
|
||||||
|
pam__in = filters.CharFilter(method='pam_filter')
|
||||||
|
p_specifique = filters.CharFilter(method='p_specifique_filter')
|
||||||
|
p_itd_affecte = filters.CharFilter(method='p_itd_affecte_filter')
|
||||||
|
formation_emploi__in = filters.CharFilter(method='formation_emploi_filter')
|
||||||
|
# sous_vivier = filters.CharFilter(method='sous_vivier_filter')
|
||||||
|
poste__in = filters.CharFilter(method='poste_filter')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Postes_Pams
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
]
|
||||||
|
|
||||||
|
def aide_a_la_decision_filter(self, queryset, name, value):
|
||||||
|
statut = [AvisPoste.P1, AvisPoste.P2, AvisPoste.P3, AvisPoste.P4]
|
||||||
|
return queryset.filter(Q(p_avis_pam__in=statut) & Q(**{f'{Postes_Pams.Cols.O2M_DECISION}__de_decision__isnull' :True}) & Q(p_pam = value))
|
||||||
|
|
||||||
|
def poste_filter(self,queryset, name, value):
|
||||||
|
values_list = value.split(',')
|
||||||
|
if values_list:
|
||||||
|
return queryset.filter(id__in=values_list)
|
||||||
|
|
||||||
|
def pam_filter(self, queryset, name, value):
|
||||||
|
values_list = value.split(',')
|
||||||
|
return queryset.filter(Q(p_pam_id__in = values_list)).distinct('poste_id')
|
||||||
|
|
||||||
|
def p_specifique_filter(self, queryset, name, value):
|
||||||
|
return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_specifique': value}))
|
||||||
|
|
||||||
|
def p_itd_affecte_filter(self, queryset, name, value):
|
||||||
|
return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_itd_affecte': value}))
|
||||||
|
|
||||||
|
def formation_emploi_filter(self, queryset, name, value):
|
||||||
|
list_values = value.split(',')
|
||||||
|
return queryset.filter(Q(**{f'{Postes_Pams.Cols.REL_POSTE}__formation_emploi__fe_mere_credo__in': list_values}))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def qs(self):
|
||||||
|
qs = super().qs
|
||||||
|
|
||||||
|
summary = get_profile_summary(self.request.user)
|
||||||
|
|
||||||
|
profiles = summary.profiles.get(KEY_READ, ())
|
||||||
|
if not profiles:
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
only_profile = self.request.query_params.get(REQ_PARAM_ONLY_PROFILE)
|
||||||
|
|
||||||
|
org_code = summary.org_code
|
||||||
|
is_fil = Profiles.FILIERE in profiles and (not only_profile or only_profile == Profiles.FILIERE)
|
||||||
|
is_pcp = Profiles.PCP in profiles and (not only_profile or only_profile == Profiles.PCP)
|
||||||
|
is_bvt = Profiles.BVT in profiles and (not only_profile or only_profile == Profiles.BVT)
|
||||||
|
is_itd = Profiles.ITD in profiles and (not only_profile or only_profile == Profiles.ITD)
|
||||||
|
if not org_code or (not is_fil and not is_pcp and not is_bvt and not is_itd):
|
||||||
|
return qs.none()
|
||||||
|
|
||||||
|
codes_lvl4 = get_lvl4_org_codes_by_any_code(org_code)
|
||||||
|
|
||||||
|
|
||||||
|
itd_filter_add = Q(**{f'{Postes_Pams.Cols.REL_POSTE}__p_specifique': SpecifiqueChoices.ITD})
|
||||||
|
poste_filter = None
|
||||||
|
if is_fil:
|
||||||
|
poste_filter = get_poste_filter_by_lvl4_codes_fil(codes_lvl4) \
|
||||||
|
| itd_filter_add
|
||||||
|
if is_pcp:
|
||||||
|
list_a_pcp_ok = Administres_Pams.objects.filter(
|
||||||
|
get_adm_filter_by_lvl4_codes_pcp(codes_lvl4)
|
||||||
|
| get_adm_filter_by_lvl4_codes_future_pcp(codes_lvl4)
|
||||||
|
).values_list('administre_id', flat=True)
|
||||||
|
|
||||||
|
pcp_filter = get_poste_filter_by_lvl4_codes_pcp(codes_lvl4) \
|
||||||
|
| itd_filter_add \
|
||||||
|
| Q(**{f'{Postes_Pams.Cols.O2M_DECISION}__{Decision.Cols.REL_ADMINISTRE}__in': list_a_pcp_ok})
|
||||||
|
poste_filter = poste_filter | pcp_filter if poste_filter else pcp_filter
|
||||||
|
|
||||||
|
if is_itd:
|
||||||
|
itd_filter = itd_filter_add
|
||||||
|
poste_filter = poste_filter | itd_filter if poste_filter else itd_filter
|
||||||
|
|
||||||
|
if is_bvt:
|
||||||
|
bvt_filter = Q(**{f'{Postes_Pams.Cols.REL_POSTE}__{Poste.Cols.M2M_SOUS_VIVIERS}': 'BVT'})
|
||||||
|
poste_filter = poste_filter | bvt_filter if poste_filter else bvt_filter
|
||||||
|
|
||||||
|
return qs.filter(poste_filter).distinct()
|
||||||
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_fields(cls):
|
||||||
|
"""
|
||||||
|
Cette fonction renvoie toutes les valeurs de recherche dans
|
||||||
|
les champs qui peuvent être filtrés. Les champs qui peuvent être filtrés sont spécifiés dans la classe Meta variable fields.
|
||||||
|
"""
|
||||||
|
fields = super().get_fields()
|
||||||
|
for field_name in fields.copy():
|
||||||
|
lookup_list = cls.Meta.model._meta.get_field(field_name).get_lookups().keys()
|
||||||
|
fields[field_name] = lookup_list
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
class RelatedOrderingFilter(OrderingFilter):
|
||||||
|
_max_related_depth = 3
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_verbose_name(field, non_verbose_name):
|
||||||
|
return field.verbose_name if hasattr(field, 'verbose_name') else non_verbose_name.replace('_', ' ')
|
||||||
|
|
||||||
|
def _retrieve_all_related_fields(self, fields, model, depth=0):
|
||||||
|
valid_fields = []
|
||||||
|
if depth > self._max_related_depth:
|
||||||
|
return valid_fields
|
||||||
|
for field in fields:
|
||||||
|
if field.related_model and field.related_model != model:
|
||||||
|
rel_fields = self._retrieve_all_related_fields(
|
||||||
|
field.related_model._meta.get_fields(),
|
||||||
|
field.related_model,
|
||||||
|
depth + 1)
|
||||||
|
for rel_field in rel_fields:
|
||||||
|
valid_fields.append((
|
||||||
|
f'{field.name}__{rel_field[0]}',
|
||||||
|
self._get_verbose_name(field, rel_field[1])
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
valid_fields.append((
|
||||||
|
field.name,
|
||||||
|
self._get_verbose_name(field, field.name),
|
||||||
|
))
|
||||||
|
return valid_fields
|
||||||
|
|
||||||
|
def get_valid_fields(self, queryset, view, context=None):
|
||||||
|
valid_fields = getattr(view, 'ordering_fields', self.ordering_fields)
|
||||||
|
if not valid_fields == '__all_related__':
|
||||||
|
if not context:
|
||||||
|
context = {}
|
||||||
|
valid_fields = super().get_valid_fields(queryset, view, context)
|
||||||
|
else:
|
||||||
|
valid_fields = [
|
||||||
|
*self._retrieve_all_related_fields(queryset.model._meta.get_fields(), queryset.model),
|
||||||
|
*[(key, key.title().split('__')) for key in queryset.query.annotations]
|
||||||
|
]
|
||||||
|
return valid_fields
|
||||||
20
backend-django/backend/form.py
Normal file
20
backend-django/backend/form.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from backend.models import PcpFeGroupe
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.admin.helpers import ActionForm
|
||||||
|
|
||||||
|
class TauxArmementForm(ActionForm):
|
||||||
|
"""Cette classe définit le formulaire du taux d'armement
|
||||||
|
"""
|
||||||
|
taux_armement_cible = forms.IntegerField(widget=forms.NumberInput, help_text="Taux d'armement cible pour la catégorie")
|
||||||
|
|
||||||
|
class GroupeFeForm(ActionForm):
|
||||||
|
"""Cette classe définit le formulaire du GroupeFe
|
||||||
|
"""
|
||||||
|
nom_groupe_fe = forms.CharField(widget=forms.TextInput, help_text="Nom du groupe de formations d'emploi")
|
||||||
|
|
||||||
|
class PcpFeGroupeForm(forms.ModelForm):
|
||||||
|
"""Cette classe définit le formulaire du PcpFeGroupe
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = PcpFeGroupe
|
||||||
|
fields = ['pcp_fe_groupe', 'pcp_fe_categorie', 'gestionnaire']
|
||||||
0
backend-django/backend/migrations/__init__.py
Normal file
0
backend-django/backend/migrations/__init__.py
Normal file
660
backend-django/backend/models.py
Normal file
660
backend-django/backend/models.py
Normal file
@@ -0,0 +1,660 @@
|
|||||||
|
"""
|
||||||
|
Ce dossier contient tous les modèles de la base de données d'OGURE
|
||||||
|
"""
|
||||||
|
# Import
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||||
|
|
||||||
|
"""
|
||||||
|
Script de création des diffferentes tables de la base
|
||||||
|
OGURE NG utilisation de l'ORM de Django pour réaliser cette étape
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# Modéle des Sous viviers de militaires et poste
|
||||||
|
class SousVivier(models.Model):
|
||||||
|
"""Modèle des Sous viviers de militaires et poste
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'sv_id'
|
||||||
|
LIBELLE = 'sv_libelle'
|
||||||
|
REL_GESTIONNAIRE = 'gestionnaire'
|
||||||
|
|
||||||
|
sv_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
gestionnaire = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
sv_libelle = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Domaines
|
||||||
|
class Domaine(models.Model):
|
||||||
|
"""Modèle des Domaines
|
||||||
|
"""
|
||||||
|
d_code = models.CharField(primary_key=True, max_length=100)
|
||||||
|
d_libelle = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.d_code,
|
||||||
|
"code": self.d_code,
|
||||||
|
"libelle": self.d_code,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Filières
|
||||||
|
class Filiere(models.Model):
|
||||||
|
"""Modèle des Filières
|
||||||
|
"""
|
||||||
|
f_code = models.CharField(primary_key=True, max_length=100)
|
||||||
|
domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True)
|
||||||
|
f_libelle_court = models.CharField(max_length=100, null=True)
|
||||||
|
f_libelle_long = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.f_code,
|
||||||
|
"domaineId": self.domaine_id,
|
||||||
|
"code": self.f_code,
|
||||||
|
"libelle": self.f_code,
|
||||||
|
"libelleCourt": self.f_code,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||||
|
class SousVivierAssociation(models.Model):
|
||||||
|
"""Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||||
|
"""
|
||||||
|
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('SOFF', 'SOFF'), ('OFF', 'OFF'), ('OGX', 'OGX')]
|
||||||
|
|
||||||
|
sva_id = models.IntegerField(primary_key=True)
|
||||||
|
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True)
|
||||||
|
filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True)
|
||||||
|
sva_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES)
|
||||||
|
sva_arme = models.CharField(max_length=20, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Grades
|
||||||
|
# FIXME le code grade et libellé sont inversés
|
||||||
|
class Grade(models.Model):
|
||||||
|
"""Modèle des Grades
|
||||||
|
"""
|
||||||
|
gr_code = models.CharField(max_length=100, primary_key=True)
|
||||||
|
gr_categorie = models.CharField(max_length=100, null=True)
|
||||||
|
gr_ordre = models.IntegerField(null=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.gr_code,
|
||||||
|
"code": self.gr_code,
|
||||||
|
# TODO corriger ci-dessous : catégorie (ex : "Sous-officiers"), libellé long (ex : "SERGENT CHEF") + ordre
|
||||||
|
"categorie": self.gr_categorie,
|
||||||
|
"ordre": self.gr_ordre
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Garnison
|
||||||
|
class Garnison(models.Model):
|
||||||
|
"""Modèle des Garnison
|
||||||
|
"""
|
||||||
|
gar_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
gar_lieu = models.CharField(max_length=100, verbose_name="Garnison")
|
||||||
|
gar_code_postal = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.gar_lieu
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle de groupe FE
|
||||||
|
class GroupeFe(models.Model):
|
||||||
|
"""Modèle de groupe FE
|
||||||
|
"""
|
||||||
|
groupe_fe_nom = models.CharField(primary_key=True, max_length=100, verbose_name='Groupe de FE')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.groupe_fe_nom
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Formation d'emplois
|
||||||
|
class FormationEmploi(models.Model):
|
||||||
|
"""Modèle des Formation d'emplois
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'fe_code'
|
||||||
|
ZONE_DEFENSE = 'zone_defense'
|
||||||
|
REL_GARNISON = 'garnison'
|
||||||
|
REL_MERE = 'mere'
|
||||||
|
|
||||||
|
fe_code = models.CharField(primary_key=True, max_length=100)
|
||||||
|
groupe_fe = models.ForeignKey(GroupeFe, blank=True, on_delete=models.SET_NULL, null=True)
|
||||||
|
garnison = models.ForeignKey(Garnison, on_delete=models.CASCADE)
|
||||||
|
fe_libelle = models.CharField(max_length=100)
|
||||||
|
fe_taux_armement_cible_off = models.FloatField(null=True, verbose_name="TA cible OFF")
|
||||||
|
fe_nb_poste_reo_off = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_reevalue_off = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_vacant_off = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_occupe_off = models.IntegerField(null=True)
|
||||||
|
fe_taux_armement_cible_soff = models.FloatField(null=True, verbose_name="TA cible SOFF")
|
||||||
|
fe_nb_poste_reo_soff = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_reevalue_soff = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_vacant_soff = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_occupe_soff = models.IntegerField(null=True)
|
||||||
|
fe_taux_armement_cible_mdr = models.FloatField(null=True, verbose_name="TA cible MDR")
|
||||||
|
fe_nb_poste_reo_mdr = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_reevalue_mdr = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_vacant_mdr = models.IntegerField(null=True)
|
||||||
|
fe_nb_poste_occupe_mdr = models.IntegerField(null=True)
|
||||||
|
|
||||||
|
zone_defense = models.CharField('zone de défense', db_column='fe_zone_defense', max_length=64, blank=True, null=True)
|
||||||
|
mere = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='FE mère', db_column='fe_mere_id')
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.fe_code,
|
||||||
|
"code": self.fe_code,
|
||||||
|
"libelle": self.fe_libelle,
|
||||||
|
"groupe_fe": self.groupe_fe_id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Groupes de marques
|
||||||
|
class MarquesGroupe(models.Model):
|
||||||
|
"""Modèle des Groupes de marques
|
||||||
|
"""
|
||||||
|
gm_id = models.IntegerField(primary_key=True)
|
||||||
|
gm_type = models.CharField(max_length=100)
|
||||||
|
gm_code = models.CharField(max_length=100)
|
||||||
|
gm_libelle = models.CharField(max_length=100)
|
||||||
|
gm_ordre = models.IntegerField(null=True)
|
||||||
|
gm_selection_multiple = models.BooleanField(null=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.gm_id,
|
||||||
|
"type": self.gm_type,
|
||||||
|
"code": self.gm_code,
|
||||||
|
"libelle": self.gm_libelle,
|
||||||
|
"selectionMultiple": self.gm_selection_multiple,
|
||||||
|
"ordre": self.gm_ordre
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Marques
|
||||||
|
class Marque(models.Model):
|
||||||
|
"""Modèle des Marques
|
||||||
|
"""
|
||||||
|
mar_id = models.TextField(primary_key=True)
|
||||||
|
groupe_marques = models.ForeignKey(MarquesGroupe, on_delete=models.CASCADE)
|
||||||
|
mar_code = models.CharField(max_length=100)
|
||||||
|
mar_libelle = models.CharField(max_length=100)
|
||||||
|
mar_ordre = models.IntegerField(null=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.mar_id,
|
||||||
|
"groupeMarquesId": self.groupe_marques_id,
|
||||||
|
"code": self.mar_code,
|
||||||
|
"libelle": self.mar_libelle,
|
||||||
|
"ordre": self.mar_ordre
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des fonctions
|
||||||
|
class Fonction(models.Model):
|
||||||
|
"""Modèle des fonctions
|
||||||
|
"""
|
||||||
|
fon_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
fon_libelle = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle Compétences
|
||||||
|
class Competence(models.Model):
|
||||||
|
"""Modèle Compétences
|
||||||
|
"""
|
||||||
|
comp_id = models.CharField(primary_key=True, max_length=100)
|
||||||
|
comp_libelle = models.CharField(null=True, max_length=100)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.comp_id,
|
||||||
|
"libelle": self.comp_libelle,
|
||||||
|
}
|
||||||
|
|
||||||
|
class SpecifiqueChoices(models.TextChoices):
|
||||||
|
"""Choix pour les propositions de CIAT d'un poste"""
|
||||||
|
SHM = 'SHM', 'SHM'
|
||||||
|
ITD = 'ITD', 'ITD'
|
||||||
|
PPE = 'PPE', 'PPE'
|
||||||
|
|
||||||
|
class PropositionsArmementChoices(models.TextChoices):
|
||||||
|
"""Choix pour les propositions d'armement d'un poste"""
|
||||||
|
|
||||||
|
PROPOSE = 'PROPOSE', 'Propositions'
|
||||||
|
VALIDE = 'VALIDE', 'Propositions validées'
|
||||||
|
|
||||||
|
# Modèle des Poste
|
||||||
|
class Poste(models.Model):
|
||||||
|
""" Modèle des Poste """
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'p_id'
|
||||||
|
# relations many-to-many
|
||||||
|
M2M_COMPETENCES = 'competences'
|
||||||
|
# relations one-to-many
|
||||||
|
O2M_DECISION = 'decisions'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_FONCTION = 'fonction'
|
||||||
|
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||||
|
REL_SOUS_VIVIER = 'sous_vivier'
|
||||||
|
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
CHOICES_NF = [('1A', '1A'), ('1B', '1B'), ('1C', '1C'), ('2.', '2.'), ('3A', '3A'), ('3B', '3B'),
|
||||||
|
('3B NFS', '3B NFS'), ('4.', '4.'), ('5A', '5A'), ('5B', '5B'), ('5C', '5C'), ('6A', '6A'),
|
||||||
|
('6B', '6B')]
|
||||||
|
AVIS = [('P1', 'P1'), ('P2', 'P2'), ('P3', 'P3'), ('P4', 'P4'), ('P4', 'P4'),('P4', 'P4'),('Gele','Gele'),('Non etudie','Non etudie')]
|
||||||
|
p_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True)
|
||||||
|
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True)
|
||||||
|
formation_emploi = models.ForeignKey(FormationEmploi, on_delete=models.SET_NULL, null=True)
|
||||||
|
competences = models.ManyToManyField(Competence)
|
||||||
|
p_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True)
|
||||||
|
p_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True)
|
||||||
|
p_fonction = models.CharField(max_length=100, null=True)
|
||||||
|
p_code_fonction = models.CharField(max_length=100, null=True)
|
||||||
|
p_nf = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||||
|
p_categorie = models.CharField(max_length=100, null=True)
|
||||||
|
p_dep = models.IntegerField(null=True)
|
||||||
|
p_liste_id_marques = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
p_eip = models.CharField(max_length=100)
|
||||||
|
p_avis = models.CharField(max_length=100, default='Non etudie', choices=AVIS)
|
||||||
|
p_notes_gestionnaire = models.TextField(null=True)
|
||||||
|
p_notes_partagees = models.TextField(null=True)
|
||||||
|
p_poids_competences = models.FloatField(null=True)
|
||||||
|
p_poids_filiere = models.FloatField(null=True)
|
||||||
|
p_poids_nf = models.FloatField(null=True)
|
||||||
|
p_nb_p1 = models.IntegerField(null=True)
|
||||||
|
p_nb_p2 = models.IntegerField(null=True)
|
||||||
|
p_nb_p3 = models.IntegerField(null=True)
|
||||||
|
p_nb_p4 = models.IntegerField(null=True)
|
||||||
|
p_nb_non_etudie = models.IntegerField(null=True)
|
||||||
|
p_nb_gele = models.IntegerField(null=True)
|
||||||
|
p_nb_reo = models.IntegerField(null=True)
|
||||||
|
p_nb_reevalue = models.IntegerField(null=True)
|
||||||
|
p_nb_occupe = models.IntegerField(null=True)
|
||||||
|
p_nb_vacant = models.IntegerField(null=True)
|
||||||
|
p_nb_affectable = models.IntegerField(null=True)
|
||||||
|
p_ciat = models.BooleanField('CIAT', default=False, null=True)
|
||||||
|
p_specifique = models.CharField('Poste spécifique', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||||
|
propositions_armement = models.CharField("propositions d'armement", db_column='p_propositions_armement', max_length=10, choices=PropositionsArmementChoices.choices, null=True, blank=True)
|
||||||
|
p_priorisation_pcp = models.TextField('Priorisation PCP', null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_p_specifique_valid',
|
||||||
|
check=models.Q(p_specifique__in=SpecifiqueChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_propositions_armement_valid',
|
||||||
|
check=models.Q(propositions_armement__in=PropositionsArmementChoices.values)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class StatutPamChoices(models.TextChoices):
|
||||||
|
"""[Administre] choix pour le statut PAM"""
|
||||||
|
|
||||||
|
A_ETUDIER = 'A_ETUDIER', 'A étudier'
|
||||||
|
A_ETUDIER_REC = 'A_ETUDIER_REC', 'A étudier REC'
|
||||||
|
A_MAINTENIR = 'A_MAINTENIR', 'A maintenir'
|
||||||
|
A_MUTER = 'A_MUTER', 'A muter'
|
||||||
|
A_TRAITER = 'A_TRAITER', 'A traiter'
|
||||||
|
NON_DISPONIBLE = 'NON_DISPONIBLE', 'Non disponible'
|
||||||
|
NON_ETUDIE = 'NON_ETUDIE', 'Non étudié'
|
||||||
|
PARTANT = 'PARTANT', 'Partant'
|
||||||
|
|
||||||
|
|
||||||
|
class StatutFuturChoices(models.TextChoices):
|
||||||
|
"""[Administre] choix pour le statut futur"""
|
||||||
|
|
||||||
|
CLDM = 'CLDM', 'CLDM'
|
||||||
|
CP = 'CP', 'CP'
|
||||||
|
DRJI = 'DRJI', 'DRJI'
|
||||||
|
NRCT = 'NRCT', 'NRCT'
|
||||||
|
DET = 'DET', 'DET'
|
||||||
|
RECONV = 'RECONV', 'RECONV'
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des administrés
|
||||||
|
class Administre(models.Model):
|
||||||
|
"""Modèle des administrés"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'a_id_sap'
|
||||||
|
STATUT_CONCERTO = 'a_statut_concerto'
|
||||||
|
STATUT_PAM = 'a_statut_pam'
|
||||||
|
DATE_STATUT_CONCERTO = 'a_date_statut_concerto'
|
||||||
|
# relations many-to-many
|
||||||
|
M2M_COMPETENCES = 'a_liste_id_competences'
|
||||||
|
# relations one-to-many
|
||||||
|
O2M_FMOB = 'fmobs'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_DECISION = 'decision'
|
||||||
|
REL_FONCTION = 'fonction'
|
||||||
|
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||||
|
REL_GRADE = 'grade'
|
||||||
|
REL_SOUS_VIVIER = 'sous_vivier'
|
||||||
|
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
|
||||||
|
CHOICES_NF = [('1A', '1A'), ('1B', '1B'), ('1C', '1C'), ('2.', '2.'), ('3A', '3A'), ('3B', '3B'),
|
||||||
|
('3B NFS', '3B NFS'), ('4.', '4.'), ('5A', '5A'), ('5B', '5B'), ('5C', '5C'), ('6A', '6A'),
|
||||||
|
('6B', '6B')]
|
||||||
|
a_id_sap = models.IntegerField(primary_key=True)
|
||||||
|
formation_emploi = models.ForeignKey(FormationEmploi, related_name="formation_emploi", on_delete=models.SET_NULL,
|
||||||
|
null=True)
|
||||||
|
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True)
|
||||||
|
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
grade = models.ForeignKey(Grade, on_delete=models.SET_NULL, null=True)
|
||||||
|
a_grade_date_debut = models.CharField(max_length=100, null=True)
|
||||||
|
a_liste_id_marques = models.CharField(max_length=100, null=True)
|
||||||
|
a_liste_id_competences = models.ManyToManyField(Competence)
|
||||||
|
a_nom = models.CharField(max_length=100)
|
||||||
|
a_prenom = models.CharField(max_length=100)
|
||||||
|
a_sexe = models.CharField(max_length=100)
|
||||||
|
a_id_def = models.CharField(max_length=100, null=True)
|
||||||
|
a_eip = models.CharField(max_length=100)
|
||||||
|
a_fonction = models.CharField(max_length=100, null=True)
|
||||||
|
a_code_fonction = models.CharField(max_length=100, null=True)
|
||||||
|
a_domaine = models.ForeignKey(Domaine, related_name="domaine", on_delete=models.SET_NULL, null=True)
|
||||||
|
a_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True)
|
||||||
|
a_nf = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||||
|
a_domaine_poste = models.ForeignKey(Domaine, related_name="domaine_poste", on_delete=models.SET_NULL, null=True)
|
||||||
|
a_filiere_poste = models.ForeignKey(Filiere, related_name="filiere_poste", on_delete=models.SET_NULL, null=True)
|
||||||
|
a_nf_poste = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||||
|
a_categorie = models.CharField(max_length=5, null=True)
|
||||||
|
a_domaine_futur = models.ForeignKey(Domaine, related_name="domaine_futur", on_delete=models.SET_NULL, null=True)
|
||||||
|
a_filiere_futur = models.ForeignKey(Filiere, related_name="filiere_futur", on_delete=models.SET_NULL, null=True)
|
||||||
|
a_nf_futur = models.CharField(max_length=100, null=True, choices=CHOICES_NF)
|
||||||
|
a_bureau_gestion = models.CharField(max_length=100, null=True)
|
||||||
|
a_date_entree_service = models.CharField(max_length=100, null=True)
|
||||||
|
a_arme = models.CharField(max_length=100, null=True)
|
||||||
|
a_rg_origine_recrutement = models.CharField(max_length=100, null=True)
|
||||||
|
a_date_naissance = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_hl = models.CharField(max_length=100, null=True)
|
||||||
|
a_dernier_diplome = models.CharField(max_length=100, null=True)
|
||||||
|
a_credo_fe = models.CharField(max_length=100, null=True)
|
||||||
|
a_date_arrivee_fe = models.CharField(max_length=100, null=True)
|
||||||
|
a_pos_statuaire = models.CharField(max_length=100, null=True)
|
||||||
|
a_date_pos_statuaire = models.CharField(max_length=100, null=True)
|
||||||
|
a_interruption_service = models.CharField(max_length=100, null=True)
|
||||||
|
a_situation_fam = models.CharField(max_length=100, null=True)
|
||||||
|
a_nombre_enfants = models.IntegerField(null=True)
|
||||||
|
a_date_rdc = models.DateField(null=True)
|
||||||
|
a_date_dernier_acr = models.DateField(null=True)
|
||||||
|
a_eis = models.CharField(max_length=100, null=True)
|
||||||
|
a_sap_conjoint = models.IntegerField(null=True)
|
||||||
|
a_flag_particulier = models.IntegerField(null=True)
|
||||||
|
a_flag_pam = models.IntegerField(null=True)
|
||||||
|
a_statut_pam = models.CharField('statut PAM', max_length=100, choices=StatutPamChoices.choices, null=True, blank=True)
|
||||||
|
a_notes_gestionnaire = models.TextField(null=True)
|
||||||
|
a_notes_partagees = models.TextField(null=True)
|
||||||
|
a_eip_futur = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation1 = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation2 = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation3 = models.CharField(max_length=100, null=True)
|
||||||
|
a_liste_depts_souhaites = models.CharField(max_length=100, null=True)
|
||||||
|
a_pls_gb_max = models.IntegerField(null=True)
|
||||||
|
a_marqueur_pn = models.BooleanField(default=False)
|
||||||
|
a_affectation4 = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation5 = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation6 = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation7 = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation8 = models.CharField(max_length=100, null=True)
|
||||||
|
a_affectation9 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction1 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction2 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction3 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction4 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction5 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction6 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction7 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction8 = models.CharField(max_length=100, null=True)
|
||||||
|
a_fonction9 = models.CharField(max_length=100, null=True)
|
||||||
|
a_profession_conjoint = models.CharField(max_length=100, null=True)
|
||||||
|
a_id_def_conjoint = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_1 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_1_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_1_note = models.FloatField(null=True)
|
||||||
|
a_diplome_2 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_2_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_2_note = models.FloatField(null=True)
|
||||||
|
a_diplome_3 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_3_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_3_note = models.FloatField(null=True)
|
||||||
|
a_diplome_4 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_4_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_4_note = models.FloatField(null=True)
|
||||||
|
a_diplome_5 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_5_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_5_note = models.FloatField(null=True)
|
||||||
|
a_diplome_6 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_6_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_6_note = models.FloatField(null=True)
|
||||||
|
a_diplome_7 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_7_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_7_note = models.FloatField(null=True)
|
||||||
|
a_diplome_8 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_8_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_8_note = models.FloatField(null=True)
|
||||||
|
a_diplome_9 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_9_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_9_note = models.FloatField(null=True)
|
||||||
|
a_diplome_10 = models.CharField(max_length=100, null=True)
|
||||||
|
a_diplome_10_date = models.DateField(max_length=100, null=True)
|
||||||
|
a_diplome_10_note = models.FloatField(null=True)
|
||||||
|
a_origine_recrutement = models.CharField(max_length=100, null=True)
|
||||||
|
statut_futur = models.CharField('statut futur', db_column='a_statut_futur', max_length=8, choices=StatutFuturChoices.choices, null=True, blank=True)
|
||||||
|
date_statut_futur = models.DateField('date du statut futur', db_column='a_date_statut_futur', null=True, blank=True)
|
||||||
|
a_statut_concerto = models.CharField('statut CONCERTO', max_length=32, null=True, blank=True)
|
||||||
|
a_date_statut_concerto = models.CharField('date du statut CONCERTO', max_length=32, null=True, blank=True)
|
||||||
|
suivi_previsionnel_situation = models.CharField('suivi prévisionnel de la situation du militaire', db_column='a_suivi_previsionnel_situation', max_length=100, null=True, blank=True)
|
||||||
|
date_suivi_previsionnel_situation = models.DateField('date du suivi prévisionnel de la situation du militaire', db_column='a_date_suivi_previsionnel_situation', null=True, blank=True)
|
||||||
|
a_annee_previsible_mutation = models.PositiveIntegerField('année prévisible de mutation', null=True)
|
||||||
|
a_fud = models.CharField('FUD de départ', max_length=32, null=True, blank=True)
|
||||||
|
a_date_fud = models.CharField('date du FUD', max_length=32, null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Administré"
|
||||||
|
verbose_name_plural = "Administrés"
|
||||||
|
constraints = [
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_statut_pam_valid',
|
||||||
|
check=models.Q(a_statut_pam__in=StatutPamChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_statut_futur_valid',
|
||||||
|
check=models.Q(statut_futur__in=StatutFuturChoices.values)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
# class CompetencesAdministre(models.Model):
|
||||||
|
# """Modèle de table intermédiaire entre Administrés et Compétences
|
||||||
|
# """
|
||||||
|
# competence = models.ForeignKey(Competence, on_delete=models.CASCADE)
|
||||||
|
# administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||||
|
# def __unicode__(self):
|
||||||
|
|
||||||
|
# return self.competence.comp_id + self.administre.a_id_sap
|
||||||
|
|
||||||
|
|
||||||
|
class FMOB(models.Model):
|
||||||
|
"""Modèle de FMOB
|
||||||
|
"""
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
|
||||||
|
fmob_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, related_name="fmobs",
|
||||||
|
db_constraint=False)
|
||||||
|
fmob_millesime = models.IntegerField(null=True)
|
||||||
|
fmob_annulation_fmob = models.BooleanField(default=False)
|
||||||
|
fmob_annulation_femp = models.BooleanField(default=False)
|
||||||
|
fmob_sans_suite_militaire_fmob = models.BooleanField(default=False)
|
||||||
|
fmob_sans_suite_militaire_femp = models.BooleanField(default=False)
|
||||||
|
fmob_date_visa_militaire = models.DateField(max_length=100, null=True)
|
||||||
|
fmob_depart_institution_soff = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_bassin_externe = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_bassin_interne = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_centre_interet_adt = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_dans_specialite = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_recrutement_particulier_administre = models.BooleanField(default=False)
|
||||||
|
fmob_motif_edition_la = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_motif_edition_ll = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_reception_drhat_fmob = models.BooleanField(default=False)
|
||||||
|
fmob_reconnaissance_parcours_pro_administre = models.BooleanField(default=False)
|
||||||
|
fmob_proposition_affectation_verrouille = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_reception_drhat_femp = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_interne = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_externe = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mutation_administre = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_centre_interet = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_specialite = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_recrutement_particulier_admin = models.BooleanField(default=False)
|
||||||
|
fmob_date_deb_fmob = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_date_fin_fmob = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_date_signature_admin_fmob = models.DateField(max_length=100, null=True)
|
||||||
|
fmob_date_signature_admin_femp = models.DateField(max_length=100, null=True)
|
||||||
|
fmob_date_signature_chef_de_corps = models.DateField(max_length=100, null=True)
|
||||||
|
fmob_remarques_eventuelles_administres = models.TextField(null=True)
|
||||||
|
fmob_avis_commandant_formation = models.TextField(null=True)
|
||||||
|
fmob_fonction_1 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_fonction_2 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_fonction_3 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_fonction_4 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_fonction_5 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_commune_1 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_commune_2 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_commune_3 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_commune_4 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_commune_5 = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_prio_1 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_2 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_3 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_4 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_5 = models.BooleanField(default=False)
|
||||||
|
fmob_avis_mutabilite = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_obs = models.CharField(max_length=100, null=True)
|
||||||
|
fmob_fe_future = models.CharField(max_length=100, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle Liste de Préference
|
||||||
|
class PreferencesListe(models.Model):
|
||||||
|
"""Modèle Liste de Préference
|
||||||
|
"""
|
||||||
|
lp_id = models.IntegerField(primary_key=True)
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||||
|
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||||
|
lp_rang_poste = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle pour le suivi des calculs
|
||||||
|
class Calcul(models.Model):
|
||||||
|
"""Modèle pour le suivi des calculs
|
||||||
|
On y intègre également le statut du calcul
|
||||||
|
"""
|
||||||
|
class Statut(models.TextChoices):
|
||||||
|
AUCUN = 'AUCUN', 'aucun'
|
||||||
|
EN_ATTENTE = 'EN_ATTENTE', 'en attente'
|
||||||
|
EN_COURS = 'EN_COURS', 'en cours'
|
||||||
|
TERMINE = 'TERMINE', 'terminé'
|
||||||
|
ERREUR = 'ERREUR', 'terminé en erreur'
|
||||||
|
TERMINE_DE_FORCE = 'TERMINE_DE_FORCE', 'terminé de force'
|
||||||
|
ARRETER = 'ARRETER'
|
||||||
|
|
||||||
|
sous_vivier = models.OneToOneField(SousVivier, primary_key=True, on_delete=models.CASCADE)
|
||||||
|
ca_date_debut = models.DateTimeField(null=True, blank=True)
|
||||||
|
ca_date_fin = models.DateTimeField(null=True, blank=True)
|
||||||
|
ca_statut = models.CharField(max_length=100)
|
||||||
|
ca_statut_pourcentage = models.FloatField(default='100')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle pour Le PAM
|
||||||
|
class PAM(models.Model):
|
||||||
|
"""Modèle pour Le PAM
|
||||||
|
"""
|
||||||
|
pam_id = models.IntegerField(primary_key=True)
|
||||||
|
pam_date = models.CharField(max_length=100)
|
||||||
|
pam_libelle = models.CharField(max_length=100)
|
||||||
|
pam_statut = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle pour les notations
|
||||||
|
class Notation(models.Model):
|
||||||
|
"""Modèle pour les notations
|
||||||
|
"""
|
||||||
|
no_id = models.AutoField(primary_key=True)
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||||
|
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||||
|
pam = models.ForeignKey(PAM, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
no_rang_administre = models.IntegerField(null=True)
|
||||||
|
no_rang_poste = models.IntegerField(null=True)
|
||||||
|
no_date_execution = models.DateTimeField(auto_now=True)
|
||||||
|
no_score_administre = models.FloatField(null=True)
|
||||||
|
no_score_poste = models.FloatField(null=True)
|
||||||
|
no_flag_cple_ideal = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle pour les décisions
|
||||||
|
class Decision(models.Model):
|
||||||
|
"""Modèle pour les décisions
|
||||||
|
"""
|
||||||
|
administre = models.OneToOneField(Administre, on_delete=models.CASCADE, primary_key=True)
|
||||||
|
poste = models.ForeignKey(Poste, on_delete=models.CASCADE, related_name="decisions")
|
||||||
|
pam = models.ForeignKey(PAM, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
de_decision = models.CharField(max_length=100)
|
||||||
|
de_date_decision = models.DateTimeField(auto_now=True)
|
||||||
|
de_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||||
|
de_notes_partagees = models.TextField(null=True, blank=True)
|
||||||
|
notation = models.ForeignKey(Notation, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle Utilisateur de l'application
|
||||||
|
class CustomUser(AbstractUser):
|
||||||
|
"""Modèle Utilisateur de l'application
|
||||||
|
"""
|
||||||
|
administre = models.ForeignKey(Administre, blank=True, on_delete=models.SET_NULL, null=True)
|
||||||
|
grade = models.CharField(blank=True, null=True, max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle d'association des groupes de FE aux gestionnaires PCP
|
||||||
|
class PcpFeGroupe(models.Model):
|
||||||
|
"""Modèle d'association des groupes de FE aux gestionnaires PCP
|
||||||
|
"""
|
||||||
|
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('OFF', 'OFF'), ('SOFF', 'SOFF'), ('OGX', 'OGX')]
|
||||||
|
pcp_fe_id = models.AutoField(primary_key=True)
|
||||||
|
pcp_fe_groupe = models.ForeignKey(GroupeFe, null=True, on_delete=models.CASCADE,
|
||||||
|
verbose_name='Nom du groupe')
|
||||||
|
pcp_fe_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES, null=True, verbose_name='Catégorie')
|
||||||
|
gestionnaire = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, db_constraint=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.gestionnaire.first_name) + ' ' + str(self.gestionnaire.last_name.upper()) + ' / ' + self.pcp_fe_groupe.groupe_fe_nom + ' / ' + self.pcp_fe_categorie
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Lien gest PCP / groupe FE'
|
||||||
|
verbose_name_plural = 'Liens gest PCP / groupe FE'
|
||||||
18
backend-django/backend/models/__init__.py
Normal file
18
backend-django/backend/models/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from .administre import *
|
||||||
|
from .calcul import *
|
||||||
|
from .commun import *
|
||||||
|
from .competence import *
|
||||||
|
from .decision import *
|
||||||
|
from .domaine import *
|
||||||
|
from .fichier_exporte import *
|
||||||
|
from .filiere import *
|
||||||
|
from .fmob import *
|
||||||
|
from .fonction import *
|
||||||
|
from .formation_emploi import *
|
||||||
|
from .garnison import *
|
||||||
|
from .grade import *
|
||||||
|
from .initial import *
|
||||||
|
from .poste import *
|
||||||
|
from .sous_vivier import *
|
||||||
|
from .pam import *
|
||||||
|
from .user import *
|
||||||
282
backend-django/backend/models/administre.py
Normal file
282
backend-django/backend/models/administre.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .commun import NiveauFonctionnelChoices, SpecifiqueChoices
|
||||||
|
from .competence import Competence
|
||||||
|
from .domaine import Domaine
|
||||||
|
from .filiere import Filiere
|
||||||
|
from .fonction import Fonction
|
||||||
|
from .formation_emploi import FormationEmploi
|
||||||
|
from .grade import Grade
|
||||||
|
from .sous_vivier import SousVivier
|
||||||
|
from .pam import PAM
|
||||||
|
class SuiviPrevisionnelMilitaire(models.TextChoices):
|
||||||
|
"""
|
||||||
|
[Administre] choix pour le gestionnaire PCP
|
||||||
|
"""
|
||||||
|
|
||||||
|
MOB_EXT = 'MOB_EXT', 'Mobilité externe'
|
||||||
|
MOB_INT = 'MOB_INT', 'Mobilité interne',
|
||||||
|
NRCT = 'NRCT', 'NRCT',
|
||||||
|
RDC = 'RDC', 'RDC',
|
||||||
|
LMT = 'LMT', 'Limite de contrat',
|
||||||
|
REC = 'REC', 'Reconversion',
|
||||||
|
REC_OE = 'REC_OE', 'Recrutement OE',
|
||||||
|
DFP = 'DFP', 'Détachement FP',
|
||||||
|
|
||||||
|
|
||||||
|
class StatutFuturChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
[Administre] choix pour le statut futur
|
||||||
|
"""
|
||||||
|
|
||||||
|
CLDM = 'CLDM', 'CLDM'
|
||||||
|
CP = 'CP', 'CP'
|
||||||
|
DRJI = 'DRJI', 'DRJI'
|
||||||
|
NRCT = 'NRCT', 'NRCT'
|
||||||
|
DET = 'DET', 'DET'
|
||||||
|
RECONV = 'RECONV', 'RECONV'
|
||||||
|
|
||||||
|
|
||||||
|
class StatutPamChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
[Administre] choix pour le statut PAM
|
||||||
|
attributs supplémentaires :
|
||||||
|
- calc_enabled : est-ce que ce statut permet le calcul ?
|
||||||
|
- dec_enabled : est-ce que ce statut permet de créer une décision ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (valeur, calc_enabled, dec_enabled), libellé
|
||||||
|
A_ETUDIER = ('A_ETUDIER', True, True), 'A étudier'
|
||||||
|
A_ETUDIER_REC = ('A_ETUDIER_REC', True, True), 'A étudier REC'
|
||||||
|
A_MAINTENIR = ('A_MAINTENIR', False, True), 'A maintenir'
|
||||||
|
A_MUTER = ('A_MUTER', True, True), 'A muter'
|
||||||
|
A_TRAITER = ('A_TRAITER', False, False), 'A traiter'
|
||||||
|
NON_DISPONIBLE = ('NON_DISPONIBLE', False, False), 'Non disponible'
|
||||||
|
NON_ETUDIE = ('NON_ETUDIE', False, False), 'Non étudié'
|
||||||
|
PARTANT = ('PARTANT', False, False), 'Partant'
|
||||||
|
|
||||||
|
def __new__(cls, value):
|
||||||
|
obj = str.__new__(cls, value[0])
|
||||||
|
obj._value_ = value[0]
|
||||||
|
obj.calc_enabled = value[1]
|
||||||
|
obj.dec_enabled = value[2]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class Administre(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des administrés
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'a_id_sap'
|
||||||
|
CATEGORIE = 'a_categorie'
|
||||||
|
STATUT_CONCERTO = 'a_statut_concerto'
|
||||||
|
STATUT_CONCERTO_FUTUR = 'a_statut_concerto_futur'
|
||||||
|
DATE_STATUT_CONCERTO = 'a_date_statut_concerto'
|
||||||
|
DATE_STATUT_CONCERTO_FUTUR = 'a_date_statut_concerto_futur'
|
||||||
|
# relations many-to-many
|
||||||
|
M2M_COMPETENCES = 'a_liste_id_competences'
|
||||||
|
M2M_PAM = 'pam'
|
||||||
|
# relations one-to-many
|
||||||
|
O2M_FMOB = 'fmobs'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_DOMAINE = 'a_domaine'
|
||||||
|
REL_FILIERE = 'a_filiere'
|
||||||
|
REL_DECISION = 'decision'
|
||||||
|
REL_FONCTION = 'fonction'
|
||||||
|
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||||
|
REL_GRADE = 'grade'
|
||||||
|
REL_SOUS_VIVIER = 'sous_vivier'
|
||||||
|
STATUT_PAM = 'a_statut_pam'
|
||||||
|
REL_PAM_INTER = 'administre'
|
||||||
|
|
||||||
|
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
|
||||||
|
a_id_sap = models.IntegerField(primary_key=True)
|
||||||
|
formation_emploi = models.ForeignKey(FormationEmploi, related_name="formation_emploi", on_delete=models.SET_NULL,
|
||||||
|
null=True, blank=True)
|
||||||
|
pam = models.ManyToManyField(PAM, through='Administres_Pams')
|
||||||
|
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
grade = models.ForeignKey(Grade, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
a_grade_date_debut = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_liste_id_marques = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_liste_id_competences = models.ManyToManyField(Competence, blank=True)
|
||||||
|
a_nom = models.CharField(max_length=100)
|
||||||
|
a_prenom = models.CharField(max_length=100)
|
||||||
|
a_sexe = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_id_def = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_eip = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_code_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True, db_constraint=False)
|
||||||
|
a_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True, db_constraint=False)
|
||||||
|
a_nf = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||||
|
a_domaine_poste = models.ForeignKey(Domaine, related_name="domaine_poste", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
a_filiere_poste = models.ForeignKey(Filiere, related_name="filiere_poste", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
a_nf_poste = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||||
|
a_categorie = models.CharField(max_length=5, null=True, blank=True)
|
||||||
|
a_domaine_futur = models.ForeignKey(Domaine, related_name="domaine_futur", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
a_filiere_futur = models.ForeignKey(Filiere, related_name="filiere_futur", on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
a_nf_futur = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||||
|
a_domaine_gestion = models.CharField('Domaine de gestion (BVT)', max_length=100, null=True, blank=True)
|
||||||
|
a_date_entree_service = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_arme = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_rg_origine_recrutement = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_naissance = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_diplome_hl = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_dernier_diplome = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_credo_fe = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_arrivee_fe = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_pos_statuaire = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_pos_statuaire = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_interruption_service = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_situation_fam = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_mariage = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_nombre_enfants = models.IntegerField(null=True, blank=True)
|
||||||
|
a_enfants = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_rdc = models.DateField(null=True, blank=True)
|
||||||
|
a_date_dernier_acr = models.DateField(null=True, blank=True)
|
||||||
|
a_eis = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_sap_conjoint = models.IntegerField(null=True, blank=True)
|
||||||
|
a_flag_particulier = models.IntegerField(null=True, blank=True)
|
||||||
|
a_notes_partagees = models.TextField(null=True, blank=True)
|
||||||
|
a_eip_fiche_detaille = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_eip_futur = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_liste_depts_souhaites = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_liste_zones_geographiques_shm = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_pls_gb_max = models.IntegerField(null=True, blank=True)
|
||||||
|
a_marqueur_pn = models.BooleanField(default=False, null=True)
|
||||||
|
a_fonction1 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction2 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction3 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction4 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction5 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction6 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction7 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction8 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_fonction9 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction1 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction2 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction3 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction4 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction5 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction6 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction7 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction8 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_date_fonction9 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_profession_conjoint = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_id_def_conjoint = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||||
|
a_statut_pam = models.CharField('statut PAM', max_length=100,choices=StatutPamChoices.choices, null=True, blank=True)
|
||||||
|
a_sexe_conjoint = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_lien_service = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_age_en_annees = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_origine_recrutement = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
statut_futur = models.CharField('statut futur', db_column='a_statut_futur', max_length=8, choices=StatutFuturChoices.choices, null=True, blank=True)
|
||||||
|
date_statut_futur = models.DateField('date du statut futur', db_column='a_date_statut_futur', null=True, blank=True)
|
||||||
|
a_statut_concerto = models.CharField('statut CONCERTO', max_length=1000, null=True, blank=True)
|
||||||
|
a_date_statut_concerto = models.CharField('date du statut CONCERTO', max_length=32, null=True, blank=True)
|
||||||
|
a_statut_concerto_futur = models.CharField('statut CONCERTO futur', max_length=100, null=True, blank=True)
|
||||||
|
a_date_statut_concerto_futur = models.CharField('date du statut CONCERTO futur', max_length=32, null=True, blank=True)
|
||||||
|
suivi_previsionnel_situation = models.CharField('suivi prévisionnel de la situation du militaire', max_length=100, choices=SuiviPrevisionnelMilitaire.choices, null=True, blank=True)
|
||||||
|
date_suivi_previsionnel_situation = models.DateField('date du suivi prévisionnel de la situation du militaire', db_column='a_date_suivi_previsionnel_situation', null=True, blank=True)
|
||||||
|
a_annee_previsible_mutation = models.PositiveIntegerField('année prévisible de mutation', null=True, blank=True)
|
||||||
|
a_fud = models.CharField('FUD de départ', max_length=32, null=True, blank=True)
|
||||||
|
a_date_fud = models.CharField('date du FUD', max_length=32, null=True, blank=True)
|
||||||
|
a_ciat = models.BooleanField('CIAT', default=False, null=True)
|
||||||
|
a_specifique = models.CharField('PPE / SHM / ITD', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||||
|
a_date_affectation = models.DateField("Date d'affectation", null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Administré"
|
||||||
|
verbose_name_plural = "Administrés"
|
||||||
|
constraints = [
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_a_nf_valid',
|
||||||
|
check=models.Q(a_nf__in=NiveauFonctionnelChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_a_nf_poste_valid',
|
||||||
|
check=models.Q(a_nf_poste__in=NiveauFonctionnelChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_a_nf_futur_valid',
|
||||||
|
check=models.Q(a_nf_futur__in=NiveauFonctionnelChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_statut_pam_valid',
|
||||||
|
check=models.Q(a_statut_pam__in=StatutPamChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_statut_futur_valid',
|
||||||
|
check=models.Q(statut_futur__in=StatutFuturChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_a_specifique',
|
||||||
|
check=models.Q(a_specifique__in=SpecifiqueChoices.values)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
class Administres_Pams(models.Model):
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'id'
|
||||||
|
REL_PAM = 'pam'
|
||||||
|
REL_ADMINISTRE = 'administre'
|
||||||
|
O2M_FMOB = 'fmobs'
|
||||||
|
REL_DECISION = 'decision'
|
||||||
|
STATUT_PAM = 'a_statut_pam_annee'
|
||||||
|
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
|
||||||
|
id = models.CharField(primary_key=True, max_length=100)
|
||||||
|
pam = models.ForeignKey(PAM, on_delete = models.CASCADE, related_name='pam')
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, related_name=Administre.Cols.REL_PAM_INTER)
|
||||||
|
a_statut_pam_annee = models.CharField('statut PAM', max_length=100,choices=StatutPamChoices.choices, null=True, blank=True)
|
||||||
|
notes_pam = models.TextField(null=True, blank=True)
|
||||||
|
a_ciat_pam = models.BooleanField('CIAT', default=False, null=True)
|
||||||
|
a_specifique_pam = models.CharField('PPE / SHM / ITD', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||||
|
a_liste_depts_souhaites_pam = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_liste_zones_geographiques_shm_pam = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
a_situationfuture_notes_fe = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Affectation(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle pour les affectations
|
||||||
|
"""
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_affec', db_constraint=False)
|
||||||
|
affect_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
affect_date = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Diplome(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle pour les diplomes
|
||||||
|
"""
|
||||||
|
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_dip', db_constraint=False)
|
||||||
|
diplome_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
diplome_date = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
diplome_note = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
diplome_niveau = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
class FUD(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle pour les formulaires unitaires de demandes
|
||||||
|
"""
|
||||||
|
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_fud', db_constraint=False)
|
||||||
|
fud_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fud_date_debut = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fud_date_fin = models.CharField(max_length=100, null=True, blank=True)
|
||||||
44
backend-django/backend/models/calcul.py
Normal file
44
backend-django/backend/models/calcul.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from .pam import PAM
|
||||||
|
|
||||||
|
from .sous_vivier import SousVivier
|
||||||
|
|
||||||
|
|
||||||
|
class StatutCalculChoices(models.TextChoices):
|
||||||
|
AUCUN = 'AUCUN', 'aucun'
|
||||||
|
EN_ATTENTE = 'EN_ATTENTE', 'en attente'
|
||||||
|
EN_ATTENTE_ARRET = 'EN_ATTENTE_ARRET', "en attente d'arrêt"
|
||||||
|
EN_COURS = 'EN_COURS', 'en cours'
|
||||||
|
TERMINE = 'TERMINE', 'terminé'
|
||||||
|
ERREUR_ADMINISTRE = 'ERREUR_ADMINISTRE','terminé en erreur administré',
|
||||||
|
ERREUR_POSTE = 'ERREUR_POSTE', 'terminé en erreur poste',
|
||||||
|
ERREUR = 'ERREUR', 'terminé en erreur'
|
||||||
|
TERMINE_DE_FORCE = 'TERMINE_DE_FORCE', 'terminé de force'
|
||||||
|
|
||||||
|
|
||||||
|
class Calcul(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle pour le suivi des calculs
|
||||||
|
On y intègre également le statut du calcul
|
||||||
|
"""
|
||||||
|
id = models.CharField(max_length=100, primary_key=True, default="")
|
||||||
|
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.CASCADE, verbose_name="Sous-vivier")
|
||||||
|
pam = models.ForeignKey(PAM, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
ca_date_debut = models.DateTimeField('Date de début', null=True, blank=True)
|
||||||
|
ca_date_fin = models.DateTimeField('Date de fin', null=True, blank=True)
|
||||||
|
ca_statut = models.CharField('Statut', max_length=40, choices=StatutCalculChoices.choices)
|
||||||
|
ca_statut_pourcentage = models.FloatField('Avancement (%)',default=0)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_ca_statut_valid',
|
||||||
|
check=models.Q(ca_statut__in=StatutCalculChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_ca_statut_pourcentage_valid',
|
||||||
|
check=Q(ca_statut_pourcentage__gte=0.0) & Q(ca_statut_pourcentage__lte=100.0)
|
||||||
|
),
|
||||||
|
]
|
||||||
31
backend-django/backend/models/commun.py
Normal file
31
backend-django/backend/models/commun.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class NiveauFonctionnelChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
Choix pour les niveaux fonctionnels
|
||||||
|
"""
|
||||||
|
|
||||||
|
N1A = '1A', '1A'
|
||||||
|
N1B = '1B', '1B'
|
||||||
|
N1C = '1C', '1C'
|
||||||
|
N2 = '2.', '2.'
|
||||||
|
N3A = '3A', '3A'
|
||||||
|
N3B = '3B', '3B'
|
||||||
|
N3B_NFS = '3B NFS', '3B NFS'
|
||||||
|
N4 = '4.', '4.'
|
||||||
|
N5A = '5A', '5A'
|
||||||
|
N5B = '5B', '5B'
|
||||||
|
N5C = '5C', '5C'
|
||||||
|
N6A = '6A', '6A'
|
||||||
|
N6B = '6B', '6B'
|
||||||
|
|
||||||
|
|
||||||
|
class SpecifiqueChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
Choix pour les propositions de CIAT
|
||||||
|
"""
|
||||||
|
|
||||||
|
SHM = 'SHM', 'SHM'
|
||||||
|
ITD = 'ITD', 'ITD'
|
||||||
|
PPE = 'PPE', 'PPE'
|
||||||
31
backend-django/backend/models/competence.py
Normal file
31
backend-django/backend/models/competence.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .domaine import Domaine
|
||||||
|
from .filiere import Filiere
|
||||||
|
|
||||||
|
|
||||||
|
class Competence(models.Model):
|
||||||
|
"""Modèle Compétences"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'comp_id'
|
||||||
|
CATEGORIE = 'comp_categorie'
|
||||||
|
LIBELLE = 'comp_libelle'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_DOMAINE = 'comp_domaine'
|
||||||
|
REL_FILIERE = 'comp_filiere'
|
||||||
|
|
||||||
|
|
||||||
|
comp_id = models.CharField('ID', primary_key=True, max_length=100)
|
||||||
|
comp_libelle = models.CharField('libellé', null=True, blank=True, max_length=100)
|
||||||
|
comp_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='domaine')
|
||||||
|
comp_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='filière')
|
||||||
|
comp_categorie = models.CharField('catégorie', max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.comp_id,
|
||||||
|
"libelle": self.comp_libelle,
|
||||||
|
}
|
||||||
113
backend-django/backend/models/decision.py
Normal file
113
backend-django/backend/models/decision.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
from enum import Enum, auto
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .administre import Administre, Administres_Pams
|
||||||
|
from .initial import PAM, Notation
|
||||||
|
from .poste import Poste, Postes_Pams
|
||||||
|
|
||||||
|
|
||||||
|
class DecisionTree(Enum):
|
||||||
|
""" enum pour les types d'arbres de décisions """
|
||||||
|
|
||||||
|
# métropole
|
||||||
|
ME = auto()
|
||||||
|
|
||||||
|
# hors métropole
|
||||||
|
HME = auto()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
|
class DecisionChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
Choix pour les statuts de décisions
|
||||||
|
attributs supplémentaires :
|
||||||
|
- trees : Tuple[DecisionTree], permet de savoir à quel(s) arbre(s) appartient le statut
|
||||||
|
"""
|
||||||
|
|
||||||
|
# arbre de décision en métropole
|
||||||
|
PROPOSITION_FE = ('PROPOSITION_FE', DecisionTree.ME), 'Proposition de FE'
|
||||||
|
DIALOGUE_EN_COURS = ('DIALOGUE_EN_COURS', DecisionTree.ME), 'Dialogue en cours'
|
||||||
|
DIALOGUE_TERMINE = ('DIALOGUE_TERMINE', DecisionTree.ME), 'Dialogue terminé'
|
||||||
|
DIALOGUE_INFRUCTUEUX = ('DIALOGUE_INFRUCTUEUX', DecisionTree.ME), 'Dialogue infructueux'
|
||||||
|
FOREMP_EN_COURS = ('FOREMP_EN_COURS', DecisionTree.ME), 'FOREMP en cours'
|
||||||
|
FOREMP_TERMINE = ('FOREMP_TERMINE', DecisionTree.ME), 'FOREMP terminé'
|
||||||
|
PREPOSITIONNE = ('PREPOSITIONNE', DecisionTree.ME), 'Prépositionné'
|
||||||
|
POSITIONNE = ('POSITIONNE', DecisionTree.ME), 'Positionné'
|
||||||
|
OMIP_EN_COURS = ('OMIP_EN_COURS', DecisionTree.ME), 'OMIP en cours'
|
||||||
|
OMIP_TERMINE = ('OMIP_TERMINE', DecisionTree.ME), 'OMIP terminé'
|
||||||
|
ATTENTE_AVIONAGE = ('ATTENTE_AVIONAGE', DecisionTree.ME), "En attente d'avionage"
|
||||||
|
OMI_EN_COURS = ('OMI_EN_COURS', DecisionTree.ME), 'OMI en cours'
|
||||||
|
OMI_ACTIVE = ('OMI_ACTIVE', DecisionTree.ME), 'OMI terminé'
|
||||||
|
OMI_ANNULE = ('OMI_ANNULE', DecisionTree.ME), 'OMI annulation'
|
||||||
|
|
||||||
|
# arbre de décision hors métropole
|
||||||
|
HME_DIALOGUE_INITIE = ('HME_DIALOGUE_INITIE', DecisionTree.HME), 'Dialogue initié (HME)'
|
||||||
|
HME_DIALOGUE_EN_COURS = ('HME_DIALOGUE_EN_COURS', DecisionTree.HME), 'Dialogue en cours (HME)'
|
||||||
|
HME_DIALOGUE_INFRUCTUEUX = ('HME_DIALOGUE_INFRUCTUEUX', DecisionTree.HME), 'Dialogue infructueux (HME)'
|
||||||
|
HME_DIALOGUE_TERMINE = ('HME_DIALOGUE_TERMINE', DecisionTree.HME), 'Dialogue terminé (HME)'
|
||||||
|
HME_PROPOSITION_VIVIER = ('HME_PROPOSITION_VIVIER', DecisionTree.HME), 'Proposition vivier HME'
|
||||||
|
HME_ETUDE_DESISTEMENT = ('HME_ETUDE_DESISTEMENT', DecisionTree.HME), 'Etude désistement (HME)'
|
||||||
|
HME_DESISTEMENT = ('HME_DESISTEMENT', DecisionTree.HME), 'Désistement activé (HME)'
|
||||||
|
HME_PREPOSITIONNE = ('HME_PREPOSITIONNE', DecisionTree.HME), 'Prépositionné (HME)'
|
||||||
|
HME_VALIDATION_EXPERT = ('HME_VALIDATION_EXPERT', DecisionTree.HME), 'Validation HME (HME)'
|
||||||
|
HME_REFUS_EXPERT = ('HME_REFUS_EXPERT', DecisionTree.HME), 'Refus HME (HME)'
|
||||||
|
HME_POSITIONNE = ('HME_POSITIONNE', DecisionTree.HME), 'Positionné (HME)'
|
||||||
|
HME_FOREMP_EN_COURS = ('HME_FOREMP_EN_COURS', DecisionTree.HME), 'FOREMP en cours (HME)'
|
||||||
|
HME_FOREMP_TERMINE = ('HME_FOREMP_TERMINE', DecisionTree.HME), 'FOREMP terminé (HME)'
|
||||||
|
HME_OMIP_EN_COURS = ('HME_OMIP_EN_COURS', DecisionTree.HME), 'OMIP en cours (HME)'
|
||||||
|
HME_OMIP_TERMINE = ('HME_OMIP_TERMINE', DecisionTree.HME), 'OMIP terminé (HME)'
|
||||||
|
HME_ATTENTE_AVIONAGE = ('HME_ATTENTE_AVIONAGE', DecisionTree.HME), "En attente d'avionage (HME)"
|
||||||
|
HME_OMI_EN_COURS = ('HME_OMI_EN_COURS', DecisionTree.HME), 'OMI en cours (HME)'
|
||||||
|
HME_OMI_ACTIVE = ('HME_OMI_ACTIVE', DecisionTree.HME), 'OMI terminé (HME)'
|
||||||
|
HME_OMI_ANNULE = ('HME_OMI_ANNULE', DecisionTree.ME), 'OMI annulation'
|
||||||
|
|
||||||
|
REMIS_A_DISPOSITION = ('REMIS_A_DISPOSITION', (DecisionTree.ME, DecisionTree.HME)), 'Remis à disposition'
|
||||||
|
|
||||||
|
def __new__(cls, value):
|
||||||
|
obj = str.__new__(cls, value[0])
|
||||||
|
obj._value_ = value[0]
|
||||||
|
_trees = value[1]
|
||||||
|
obj.trees = _trees if isinstance(_trees, tuple) else (_trees,) if _trees else ()
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle pour les décisions
|
||||||
|
class Decision(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle pour les décisions
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
STATUT = 'de_decision'
|
||||||
|
DATE = 'de_date_decision'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_ADMINISTRE = 'administre'
|
||||||
|
REL_POSTE = 'poste'
|
||||||
|
|
||||||
|
administre_pam = models.OneToOneField(Administres_Pams, on_delete=models.CASCADE, related_name=Administres_Pams.Cols.REL_DECISION, primary_key=True, default="")
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, related_name=Administres_Pams.Cols.REL_DECISION)
|
||||||
|
poste = models.ForeignKey(Poste, on_delete=models.CASCADE, related_name= Postes_Pams.Cols.O2M_DECISION)
|
||||||
|
poste_pam = models.OneToOneField(Postes_Pams, on_delete=models.CASCADE, related_name= Postes_Pams.Cols.O2M_DECISION, default="")
|
||||||
|
de_decision = models.CharField('Décision', max_length=50, choices=DecisionChoices.choices)
|
||||||
|
de_date_decision = models.DateTimeField('Date de décision', auto_now=True)
|
||||||
|
de_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||||
|
de_notes_partagees = models.TextField(null=True, blank=True)
|
||||||
|
notation = models.ForeignKey(Notation, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_de_decision_valid',
|
||||||
|
check=models.Q(de_decision__in=DecisionChoices.values)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
verbose_name = 'Décision'
|
||||||
|
verbose_name_plural = 'Décisions'
|
||||||
21
backend-django/backend/models/domaine.py
Normal file
21
backend-django/backend/models/domaine.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Domaine(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des domaines
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'd_code'
|
||||||
|
|
||||||
|
d_code = models.CharField('code', primary_key=True, max_length=100)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.d_code,
|
||||||
|
'code': self.d_code,
|
||||||
|
'libelle': self.d_code,
|
||||||
|
}
|
||||||
11
backend-django/backend/models/fichier_exporte.py
Normal file
11
backend-django/backend/models/fichier_exporte.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class FichiersExporte(models.Model):
|
||||||
|
"""Modèle de fichiers exportés"""
|
||||||
|
|
||||||
|
nom_fichier = models.CharField(null=True, blank=True, max_length=100)
|
||||||
|
date_export = models.DateTimeField(null=True, blank=True, auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Fichier exporté'
|
||||||
|
verbose_name_plural = 'Fichiers exportés'
|
||||||
28
backend-django/backend/models/filiere.py
Normal file
28
backend-django/backend/models/filiere.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .domaine import Domaine
|
||||||
|
|
||||||
|
|
||||||
|
class Filiere(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des filières
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'f_code'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_DOMAINE = 'domaine'
|
||||||
|
|
||||||
|
|
||||||
|
f_code = models.CharField('code', primary_key=True, max_length=100)
|
||||||
|
domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
'id': self.f_code,
|
||||||
|
'domaineId': self.domaine_id,
|
||||||
|
'code': self.f_code,
|
||||||
|
'libelle': self.f_code,
|
||||||
|
}
|
||||||
71
backend-django/backend/models/fmob.py
Normal file
71
backend-django/backend/models/fmob.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .administre import Administre, Administres_Pams
|
||||||
|
|
||||||
|
|
||||||
|
class FMOB(models.Model):
|
||||||
|
"""Modèle de FMOB
|
||||||
|
"""
|
||||||
|
CHOICES_STATUT = [('Annulé', 'Annulé'), ('Classé sans suite', 'Classé sans suite'), ('Non réceptionné', 'Non réceptionné'),
|
||||||
|
('Réceptionné', 'Réceptionné')]
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
|
||||||
|
fmob_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
administre = models.OneToOneField(Administres_Pams, on_delete=models.CASCADE, null=True, blank=True, related_name='fmobs')
|
||||||
|
#administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name=Administre.Cols.O2M_FMOB, db_constraint=False)
|
||||||
|
fmob_millesime = models.IntegerField(null=True, blank=True)
|
||||||
|
fmob_millesime_femp = models.IntegerField(null=True, blank=True)
|
||||||
|
fmob_reception_drhat_fmob = models.BooleanField(default=False)
|
||||||
|
fmob_annulation_fmob = models.BooleanField(default=False)
|
||||||
|
fmob_annulation_femp = models.BooleanField(default=False)
|
||||||
|
fmob_sans_suite_militaire_fmob = models.BooleanField(default=False)
|
||||||
|
fmob_sans_suite_militaire_femp = models.BooleanField(default=False)
|
||||||
|
fmob_date_visa_militaire = models.DateField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_depart_institution_soff = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_bassin_externe = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_bassin_interne = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_centre_interet_adt = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_dans_specialite = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||||
|
fmob_mobilite_recrutement_particulier_administre = models.BooleanField(default=False)
|
||||||
|
fmob_motif_edition_la = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_motif_edition_ll = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_reception_drhat_fmob = models.BooleanField(default=False)
|
||||||
|
fmob_reconnaissance_parcours_pro_administre = models.BooleanField(default=False)
|
||||||
|
fmob_proposition_affectation_verrouille = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_reception_drhat_femp = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_interne = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_externe = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mutation_administre = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_centre_interet = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_specialite = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_hors_metropole = models.BooleanField(default=False)
|
||||||
|
fmob_avis_cdc_mobilite_recrutement_particulier_admin = models.BooleanField(default=False)
|
||||||
|
fmob_date_deb_fmob = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_date_fin_fmob = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_date_signature_admin_fmob = models.DateField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_date_signature_admin_femp = models.DateField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_date_signature_chef_de_corps = models.DateField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_remarques_eventuelles_administres = models.TextField(null=True, blank=True)
|
||||||
|
fmob_avis_commandant_formation = models.TextField(null=True, blank=True)
|
||||||
|
fmob_fonction_1 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_fonction_2 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_fonction_3 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_fonction_4 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_fonction_5 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_commune_1 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_commune_2 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_commune_3 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_commune_4 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_commune_5 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_prio_1 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_2 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_3 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_4 = models.BooleanField(default=False)
|
||||||
|
fmob_prio_5 = models.BooleanField(default=False)
|
||||||
|
fmob_avis_mutabilite = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_obs = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_fe_future = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fmob_statut = models.CharField(max_length=100, null=True, blank=True, choices=CHOICES_STATUT)
|
||||||
|
fmob_commentaire_ac = models.TextField(null=True, blank=True)
|
||||||
15
backend-django/backend/models/fonction.py
Normal file
15
backend-django/backend/models/fonction.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Fonction(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des fonctions
|
||||||
|
"""
|
||||||
|
|
||||||
|
fon_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
fon_libelle = models.CharField(max_length=100)
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.fon_id,
|
||||||
|
"libelle": self.fon_libelle,
|
||||||
|
}
|
||||||
96
backend-django/backend/models/formation_emploi.py
Normal file
96
backend-django/backend/models/formation_emploi.py
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .garnison import Garnison
|
||||||
|
from .user import CustomUser
|
||||||
|
|
||||||
|
|
||||||
|
class GroupeFe(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle de groupes FE
|
||||||
|
"""
|
||||||
|
groupe_fe_nom = models.CharField(primary_key=True, max_length=100, verbose_name='Groupe de FE')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.groupe_fe_nom
|
||||||
|
|
||||||
|
|
||||||
|
class PcpFeGroupe(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle d'association des groupes de FE aux gestionnaires PCP
|
||||||
|
"""
|
||||||
|
|
||||||
|
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('OFF', 'OFF'), ('SOFF', 'SOFF'), ('OGX', 'OGX')]
|
||||||
|
pcp_fe_id = models.AutoField(primary_key=True)
|
||||||
|
pcp_fe_groupe = models.ForeignKey(GroupeFe, null=True, blank=True, on_delete=models.CASCADE,
|
||||||
|
verbose_name='Nom du groupe')
|
||||||
|
pcp_fe_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES, null=True, blank=True, verbose_name='Catégorie')
|
||||||
|
gestionnaire = models.ForeignKey(CustomUser, on_delete=models.CASCADE, null=True, blank=True, db_constraint=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.gestionnaire.first_name) + ' ' + str(self.gestionnaire.last_name.upper()) + ' / ' + self.pcp_fe_groupe.groupe_fe_nom + ' / ' + self.pcp_fe_categorie
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Lien gest PCP / groupe FE'
|
||||||
|
verbose_name_plural = 'Liens gest PCP / groupe FE'
|
||||||
|
|
||||||
|
|
||||||
|
class FormationEmploi(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des formations d'emplois
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'fe_code'
|
||||||
|
LIBELLE = 'fe_libelle'
|
||||||
|
ZONE_DEFENSE = 'zone_defense'
|
||||||
|
REL_MERE = 'mere'
|
||||||
|
REL_POSTE = 'poste'
|
||||||
|
M2M_GESTIONNAIRE = 'gestionnaires'
|
||||||
|
|
||||||
|
fe_code = models.CharField(primary_key=True, max_length=100)
|
||||||
|
groupe_fe = models.ForeignKey(GroupeFe, blank=True, on_delete=models.SET_NULL, null=True)
|
||||||
|
gestionnaires = models.ManyToManyField(CustomUser, related_name=CustomUser.Cols.M2M_FORMATION_EMPLOIS, blank=True)
|
||||||
|
fe_code_postal = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fe_garnison_lieu = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fe_libelle = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fe_taux_armement_cible_off = models.FloatField(null=True, blank=True, verbose_name="TA cible OFF")
|
||||||
|
fe_nb_poste_reo_off = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_reevalue_off = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_vacant_off = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_occupe_off = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_taux_armement_cible_soff = models.FloatField(null=True, blank=True, verbose_name="TA cible SOFF")
|
||||||
|
fe_nb_poste_reo_soff = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_reevalue_soff = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_vacant_soff = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_occupe_soff = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_taux_armement_cible_mdr = models.FloatField(null=True, blank=True, verbose_name="TA cible MDR")
|
||||||
|
fe_nb_poste_reo_mdr = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_reevalue_mdr = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_vacant_mdr = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_nb_poste_occupe_mdr = models.IntegerField(null=True, blank=True)
|
||||||
|
fe_mere_credo = models.CharField('Code FE mère', max_length=100, null=True, blank=True)
|
||||||
|
fe_mere_la = models.CharField('Libelle FE mère', max_length=100, null=True, blank=True)
|
||||||
|
fe_fot = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fe_abo_fe = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fe_pilier_niv1 = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
fe_code_niv_org4 = models.CharField('Code niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||||
|
fe_niv_org4 = models.CharField('Libelle niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||||
|
fe_code_niv_org4_mdr = models.CharField('Code niveau d\'org 4 (MDR)', max_length=100, null=True, blank=True)
|
||||||
|
fe_niv_org4_mdr = models.CharField('Libelle niveau d\'org 4 (MDR)', max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
zone_defense = models.CharField('zone de défense', db_column='fe_zone_defense', max_length=64, blank=True, null=True)
|
||||||
|
mere = models.ForeignKey('self', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='FE mère', db_column='fe_mere_id')
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.fe_code,
|
||||||
|
"code": self.fe_code,
|
||||||
|
"libelle": self.fe_libelle,
|
||||||
|
"mere_code": self.fe_mere_credo,
|
||||||
|
"mere_la": self.fe_mere_la,
|
||||||
|
"groupe_fe": self.groupe_fe_id,
|
||||||
|
"fe_garnison_lieu": self.fe_garnison_lieu,
|
||||||
|
"zone_defense": self.zone_defense
|
||||||
|
}
|
||||||
21
backend-django/backend/models/garnison.py
Normal file
21
backend-django/backend/models/garnison.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class Garnison(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des garnisons
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'gar_id'
|
||||||
|
LIEU = 'gar_lieu'
|
||||||
|
CODE_POSTAL = 'gar_code_postal'
|
||||||
|
|
||||||
|
gar_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
gar_lieu = models.CharField(max_length=100, verbose_name="Garnison")
|
||||||
|
gar_code_postal = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.gar_lieu
|
||||||
21
backend-django/backend/models/grade.py
Normal file
21
backend-django/backend/models/grade.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME le code grade et libellé sont inversés
|
||||||
|
class Grade(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des grades
|
||||||
|
"""
|
||||||
|
|
||||||
|
gr_code = models.CharField(max_length=100, primary_key=True)
|
||||||
|
gr_categorie = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
gr_ordre = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.gr_code,
|
||||||
|
"code": self.gr_code,
|
||||||
|
# TODO corriger ci-dessous : catégorie (ex : "Sous-officiers"), libellé long (ex : "SERGENT CHEF") + ordre
|
||||||
|
"categorie": self.gr_categorie,
|
||||||
|
"ordre": self.gr_ordre
|
||||||
|
}
|
||||||
231
backend-django/backend/models/initial.py
Normal file
231
backend-django/backend/models/initial.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
"""
|
||||||
|
Ce dossier contient tous les modèles de la base de données d'OGURE
|
||||||
|
"""
|
||||||
|
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from .administre import Administre, Administres_Pams
|
||||||
|
from .commun import NiveauFonctionnelChoices, SpecifiqueChoices
|
||||||
|
from .competence import Competence
|
||||||
|
from .domaine import Domaine
|
||||||
|
from .filiere import Filiere
|
||||||
|
from .garnison import Garnison
|
||||||
|
from .grade import Grade
|
||||||
|
from .poste import Poste, Postes_Pams
|
||||||
|
from .sous_vivier import SousVivier
|
||||||
|
from .pam import PAM
|
||||||
|
|
||||||
|
"""
|
||||||
|
Script de création des diffferentes tables de la base
|
||||||
|
OGURE NG utilisation de l'ORM de Django pour réaliser cette étape
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: supprimer ce modèle et le remplacer par le nouveau modèle des Sous-viviers
|
||||||
|
# Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||||
|
class SousVivierAssociation(models.Model):
|
||||||
|
"""Modèle de l'association des sous-viviers à la catégorie et la filière
|
||||||
|
"""
|
||||||
|
CATEGORIE_CHOICES = [('MDR', 'MDR'), ('SOFF', 'SOFF'), ('OFF', 'OFF'), ('OGX', 'OGX')]
|
||||||
|
|
||||||
|
sva_id = models.IntegerField(primary_key=True)
|
||||||
|
sous_vivier = models.ForeignKey(SousVivier, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
sva_categorie = models.CharField(max_length=5, choices=CATEGORIE_CHOICES)
|
||||||
|
sva_arme = models.CharField(max_length=20, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Groupes de marques
|
||||||
|
class MarquesGroupe(models.Model):
|
||||||
|
"""Modèle des Groupes de marques
|
||||||
|
"""
|
||||||
|
gm_id = models.IntegerField(primary_key=True)
|
||||||
|
gm_type = models.CharField(max_length=100)
|
||||||
|
gm_code = models.CharField(max_length=100)
|
||||||
|
gm_libelle = models.CharField(max_length=100)
|
||||||
|
gm_ordre = models.IntegerField(null=True, blank=True)
|
||||||
|
gm_selection_multiple = models.BooleanField(null=True, blank=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.gm_id,
|
||||||
|
"type": self.gm_type,
|
||||||
|
"code": self.gm_code,
|
||||||
|
"libelle": self.gm_libelle,
|
||||||
|
"selectionMultiple": self.gm_selection_multiple,
|
||||||
|
"ordre": self.gm_ordre
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle des Marques
|
||||||
|
class Marque(models.Model):
|
||||||
|
"""Modèle des Marques
|
||||||
|
"""
|
||||||
|
mar_id = models.TextField(primary_key=True)
|
||||||
|
groupe_marques = models.ForeignKey(MarquesGroupe, on_delete=models.CASCADE)
|
||||||
|
mar_code = models.CharField(max_length=100)
|
||||||
|
mar_libelle = models.CharField(max_length=100)
|
||||||
|
mar_ordre = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.mar_id,
|
||||||
|
"groupeMarquesId": self.groupe_marques_id,
|
||||||
|
"code": self.mar_code,
|
||||||
|
"libelle": self.mar_libelle,
|
||||||
|
"ordre": self.mar_ordre
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ZoneGeographique(models.Model):
|
||||||
|
"""Modèle des Zones Geographiques
|
||||||
|
"""
|
||||||
|
zone_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
zone_libelle = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"id": self.zone_id,
|
||||||
|
"libelle": self.zone_libelle
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle Liste de Préference
|
||||||
|
class PreferencesListe(models.Model):
|
||||||
|
"""Modèle Liste de Préference
|
||||||
|
"""
|
||||||
|
lp_id = models.IntegerField(primary_key=True)
|
||||||
|
administre_pam = models.ForeignKey(Administres_Pams, on_delete=models.CASCADE, related_name= 'administre_pam_pref', default="")
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, related_name= 'administre_pref', default="")
|
||||||
|
pam = models.ForeignKey(PAM, on_delete=models.CASCADE, default="")
|
||||||
|
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||||
|
lp_rang_poste = models.IntegerField()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle pour les notations
|
||||||
|
class Notation(models.Model):
|
||||||
|
"""Modèle pour les notations
|
||||||
|
"""
|
||||||
|
no_id = models.AutoField(primary_key=True)
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE)
|
||||||
|
poste = models.ForeignKey(Poste, on_delete=models.CASCADE)
|
||||||
|
administre_pam = models.ForeignKey(Administres_Pams, on_delete=models.CASCADE, default="", related_name='administre_pam')
|
||||||
|
poste_pam = models.ForeignKey(Postes_Pams, on_delete=models.CASCADE, default="", related_name='poste_pam')
|
||||||
|
pam = models.ForeignKey(PAM, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
no_rang_administre = models.IntegerField(null=True, blank=True)
|
||||||
|
no_rang_poste = models.IntegerField(null=True, blank=True)
|
||||||
|
no_date_execution = models.DateTimeField(auto_now=True)
|
||||||
|
no_score_administre = models.FloatField(null=True, blank=True)
|
||||||
|
no_score_poste = models.FloatField(null=True, blank=True)
|
||||||
|
no_flag_cple_ideal = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Administre_Notation(models.Model):
|
||||||
|
"""Modèle le lien entre Notation et Administre
|
||||||
|
"""
|
||||||
|
id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
administre = models.ForeignKey(Administre, on_delete=models.CASCADE, null=True, blank=True, related_name='adm_not', db_constraint=False)
|
||||||
|
no_annne_de_notation = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
no_nr_ou_iris = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
no_rac_ou_iris_cumule = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
no_rf_qsr = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
no_aptitude_emploie_sup = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
no_potentiel_responsabilite_sup = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
no_age_annees = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle du référentiel organique
|
||||||
|
class RefOrg(models.Model):
|
||||||
|
"""Modèle de référentiel organique"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'ref_org_code'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_REF_GEST = 'ref_gest'
|
||||||
|
|
||||||
|
# TODO: ajouter la condition d'unicité des lignes (utiliser class Meta)
|
||||||
|
ref_org_code = models.CharField('Code niveau d\'org', max_length=100, primary_key=True)
|
||||||
|
ref_org_code_niv_org1 = models.CharField('Code niveau d\'org 1', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_lib_niv_org1 = models.CharField('Livelle niveau d\'org 1', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_code_niv_org2 = models.CharField('Code niveau d\'org 2', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_lib_niv_org2 = models.CharField('Libelle niveau d\'org 2', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_code_niv_org3 = models.CharField('Code niveau d\'org 3', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_lib_niv_org3 = models.CharField('Libelle niveau d\'org 3', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_code_niv_org4 = models.CharField('Code niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_lib_niv_org4 = models.CharField('Libell niveau d\'org 4', max_length=100, null=True, blank=True)
|
||||||
|
ref_org_niv_org = models.IntegerField('Niveau d\'org', null=True, blank=True)
|
||||||
|
ref_org_ref_fe = models.BooleanField('Pameur BMOB', default=False, null=True)
|
||||||
|
ref_org_ref_sv_fil = models.BooleanField('Gestionnaire BGCAT', default=False, null=True)
|
||||||
|
ref_org_droit_lect = models.BooleanField('Droit de lecture', default=False, null=True)
|
||||||
|
ref_org_droit_ecr = models.BooleanField("Droit d'écriture", default=False, null=True)
|
||||||
|
ref_org_expert_hme = models.BooleanField('Expert HME', default=False, null=True)
|
||||||
|
ref_org_bvt = models.BooleanField('Gestionnaire BVT', default=False, null=True)
|
||||||
|
ref_org_itd = models.BooleanField('Gestionnaire ITD', default=False, null=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Référentiel organique'
|
||||||
|
verbose_name_plural = 'Référentiels organiques'
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle du référentiel gestionnaire
|
||||||
|
class RefGest(models.Model):
|
||||||
|
"""Modèle de référentiel gestionnaire"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'ref_gest_sap'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_ORG = 'ref_gest_org'
|
||||||
|
|
||||||
|
ref_gest_sap = models.IntegerField('Id SAP', primary_key=True)
|
||||||
|
ref_gest_username = models.CharField('Nom d\'utilisateur', max_length=100, null=True, blank=True)
|
||||||
|
ref_gest_email = models.CharField('Adresse mail', max_length=100, null=True, blank=True)
|
||||||
|
ref_gest_first_name = models.CharField('Prénom', max_length=100, null=True, blank=True)
|
||||||
|
ref_gest_last_name = models.CharField('Nom', max_length=100, null=True, blank=True)
|
||||||
|
ref_gest_grade = models.CharField('Grade', max_length=100, null=True, blank=True)
|
||||||
|
ref_gest_niv_org = models.CharField('Niveau d\'org', max_length=100, null=True, blank=True)
|
||||||
|
ref_gest_org = models.ForeignKey(RefOrg, verbose_name='Référentiel organique', related_name=RefOrg.Cols.REL_REF_GEST, on_delete=models.CASCADE,
|
||||||
|
db_column='ref_gest_org_id', null=True, blank=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Référentiel gestionnaire'
|
||||||
|
verbose_name_plural = 'Référentiels gestionnaires'
|
||||||
|
|
||||||
|
|
||||||
|
# Modèle du référentiel sous-vivier filière
|
||||||
|
class RefSvFil(models.Model):
|
||||||
|
"""Modèle de référentiel sous-vivier filière
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_SOUS_VIVIER = 'sous_vivier'
|
||||||
|
|
||||||
|
|
||||||
|
ref_sv_fil_code = models.CharField('Code niveau d\'org', max_length=100, null=True, blank=True)
|
||||||
|
ref_sv_fil_dom_gest = models.CharField('Domaine de gestion', max_length=100, null=True, blank=True)
|
||||||
|
ref_sv_fil_dom = models.CharField('Domaine', max_length=100, null=True, blank=True)
|
||||||
|
ref_sv_fil_fil = models.CharField('Filière', max_length=100, null=True, blank=True)
|
||||||
|
ref_sv_fil_cat = models.CharField('Catégorie', max_length=100, null=True, blank=True)
|
||||||
|
sous_vivier = models.ForeignKey(SousVivier, verbose_name='Sous-vivier', on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"ref_sv_fil_code": self.ref_sv_fil_code,
|
||||||
|
"ref_sv_fil_dom_gest": self.ref_sv_fil_dom_gest,
|
||||||
|
"ref_sv_fil_dom": self.ref_sv_fil_dom,
|
||||||
|
"ref_sv_fil_fil": self.ref_sv_fil_fil,
|
||||||
|
"ref_sv_fil_cat": self.ref_sv_fil_cat,
|
||||||
|
}
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Référentiel sous-vivier/filière'
|
||||||
|
verbose_name_plural = 'Référentiels sous-viviers/filières'
|
||||||
18
backend-django/backend/models/pam.py
Normal file
18
backend-django/backend/models/pam.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PAM(models.Model):
|
||||||
|
"""Modèle pour Le PAM
|
||||||
|
"""
|
||||||
|
pam_id = models.CharField(primary_key=True,max_length=100, default=False)
|
||||||
|
pam_date = models.CharField(max_length=100)
|
||||||
|
pam_libelle = models.CharField(max_length=100)
|
||||||
|
pam_statut = models.CharField(max_length=100)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
return {
|
||||||
|
"pam_id": self.pam_id,
|
||||||
|
"pam_libelle": self.pam_libelle,
|
||||||
|
"pam_statut": self.pam_statut,
|
||||||
|
}
|
||||||
189
backend-django/backend/models/poste.py
Normal file
189
backend-django/backend/models/poste.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
from bulk_update_or_create import BulkUpdateOrCreateQuerySet
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .administre import Administre
|
||||||
|
from .commun import NiveauFonctionnelChoices, SpecifiqueChoices
|
||||||
|
from .competence import Competence
|
||||||
|
from .domaine import Domaine
|
||||||
|
from .filiere import Filiere
|
||||||
|
from .fonction import Fonction
|
||||||
|
from .formation_emploi import FormationEmploi
|
||||||
|
from .sous_vivier import SousVivier
|
||||||
|
from .pam import PAM
|
||||||
|
|
||||||
|
class AvisPosteChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
[Poste] choix pour les avis
|
||||||
|
attributs supplémentaires :
|
||||||
|
- calc_enabled : est-ce que cet avis permet le calcul ?
|
||||||
|
- dec_enabled : est-ce que cet avis permet de créer une décision ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
# (valeur, calc_enabled, dec_enabled), libellé
|
||||||
|
P1 = ('P1', True, True), 'Priorité 1'
|
||||||
|
P2 = ('P2', True, True), 'Priorité 2'
|
||||||
|
P3 = ('P3', True, True), 'Priorité 3'
|
||||||
|
P4 = ('P4', True, True), 'Priorité 4'
|
||||||
|
GELE = ('GELE', False, False), 'Gelé'
|
||||||
|
NON_ETUDIE = ('NON_ETUDIE', False, False), 'Non étudié'
|
||||||
|
|
||||||
|
def __new__(cls, value):
|
||||||
|
obj = str.__new__(cls, value[0])
|
||||||
|
obj._value_ = value[0]
|
||||||
|
obj.calc_enabled = value[1]
|
||||||
|
obj.dec_enabled = value[2]
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class DirectCommissionneChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
[Poste] choix pour direct/commissionné
|
||||||
|
"""
|
||||||
|
|
||||||
|
DIRECT = 'DIRECT', 'Direct'
|
||||||
|
COMMISSIONNE = 'COMMISSIONNE', 'Commissionné'
|
||||||
|
|
||||||
|
|
||||||
|
class PropositionsArmementChoices(models.TextChoices):
|
||||||
|
"""
|
||||||
|
[Poste] choix pour les propositions d'armement
|
||||||
|
"""
|
||||||
|
|
||||||
|
PROPOSE = 'PROPOSE', 'Propositions'
|
||||||
|
VALIDE = 'VALIDE', 'Propositions validées'
|
||||||
|
|
||||||
|
|
||||||
|
class Poste(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des postes
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'p_id'
|
||||||
|
CATEGORIE = 'p_categorie'
|
||||||
|
FONCTION = 'p_code_fonction'
|
||||||
|
NIVEAU_FONCTIONNEL = 'p_nf'
|
||||||
|
# relations many-to-many
|
||||||
|
M2M_COMPETENCES = 'competences'
|
||||||
|
M2M_SOUS_VIVIERS = 'sous_viviers'
|
||||||
|
M2M_PAM = 'p_pam'
|
||||||
|
# relations one-to-many
|
||||||
|
O2M_DECISION = 'decisions'
|
||||||
|
# relations one-to-one ou many-to-one
|
||||||
|
REL_ADMINISTRE = 'p_administre'
|
||||||
|
REL_DOMAINE = 'p_domaine'
|
||||||
|
REL_FILIERE = 'p_filiere'
|
||||||
|
REL_FONCTION = 'fonction'
|
||||||
|
REL_FORMATION_EMPLOI = 'formation_emploi'
|
||||||
|
|
||||||
|
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
|
||||||
|
p_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
p_pam = models.ManyToManyField(PAM, through='Postes_Pams')
|
||||||
|
p_annee = models.CharField(max_length=100, default=False)
|
||||||
|
p_administre = models.ForeignKey(Administre, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
fonction = models.ForeignKey(Fonction, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
sous_viviers = models.ManyToManyField(SousVivier, related_name=SousVivier.Cols.M2M_POSTES, blank=True)
|
||||||
|
formation_emploi = models.ForeignKey(FormationEmploi, on_delete=models.SET_NULL, related_name=FormationEmploi.Cols.REL_POSTE, null=True, blank=True)
|
||||||
|
competences = models.ManyToManyField(Competence, blank=True)
|
||||||
|
p_domaine = models.ForeignKey(Domaine, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
p_filiere = models.ForeignKey(Filiere, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
|
p_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
p_code_fonction = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
p_nf = models.CharField(max_length=100, null=True, blank=True, choices=NiveauFonctionnelChoices.choices)
|
||||||
|
p_categorie = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
p_dep = models.CharField('Département', max_length=2, null=True, blank=True)
|
||||||
|
p_liste_id_marques = models.CharField('Marques PAM en cours', max_length=100, null=True, blank=True)
|
||||||
|
p_eip = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
p_avis = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||||
|
p_avis_fe = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||||
|
p_notes_gestionnaire = models.TextField(null=True, blank=True)
|
||||||
|
p_notes_partagees = models.TextField(null=True, blank=True)
|
||||||
|
p_ciat = models.BooleanField('CIAT', default=False, null=True)
|
||||||
|
p_specifique = models.CharField('PPE / SHM / ITD', max_length=250, choices=SpecifiqueChoices.choices, null=True, blank=True)
|
||||||
|
p_direct_commissionne = models.CharField('Direct Commissionne', max_length=100, choices=DirectCommissionneChoices.choices, null=True, blank=True)
|
||||||
|
propositions_armement = models.CharField("propositions d'armement", db_column='p_propositions_armement', max_length=10, choices=PropositionsArmementChoices.choices, null=True, blank=True)
|
||||||
|
p_priorisation_pcp = models.TextField('Priorisation PCP', null=True, blank=True)
|
||||||
|
p_nfs = models.CharField('Domaine de gestion (BVT)', max_length=100, null=True, blank=True)
|
||||||
|
p_itd_cellule = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
p_itd_affecte = models.BooleanField(null=True, blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def p_poids_competences(self):
|
||||||
|
"le poids des competences"
|
||||||
|
if self.p_specifique:
|
||||||
|
return 65 if self.competences.exists() else 0
|
||||||
|
else:
|
||||||
|
return 60 if self.competences.exists() else 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def p_poids_filiere(self):
|
||||||
|
"le poids des filieres"
|
||||||
|
if self.p_specifique:
|
||||||
|
return 0 if self.competences.exists() else 20
|
||||||
|
else:
|
||||||
|
return 10 if self.competences.exists() else 75
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def p_poids_nf(self):
|
||||||
|
"le poids des niveaux fonctionnels"
|
||||||
|
if self.p_specifique:
|
||||||
|
return 35 if self.competences.exists() else 80
|
||||||
|
else:
|
||||||
|
return 30 if self.competences.exists() else 25
|
||||||
|
|
||||||
|
@property
|
||||||
|
def p_poids_filiere_nf_competences(self):
|
||||||
|
"le poids des filieres, nf et competences"
|
||||||
|
if self.p_specifique:
|
||||||
|
return (0, 35, 65) if self.competences.exists() else (20, 80, 0)
|
||||||
|
else:
|
||||||
|
return (10, 30, 60) if self.competences.exists() else (75, 25, 0)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_p_direct_commissionne_valid',
|
||||||
|
check=models.Q(p_direct_commissionne__in=DirectCommissionneChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_p_nf_valid',
|
||||||
|
check=models.Q(p_nf__in=NiveauFonctionnelChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_p_specifique_valid',
|
||||||
|
check=models.Q(p_specifique__in=SpecifiqueChoices.values)
|
||||||
|
),
|
||||||
|
models.CheckConstraint(
|
||||||
|
name='%(app_label)s_%(class)s_propositions_armement_valid',
|
||||||
|
check=models.Q(propositions_armement__in=PropositionsArmementChoices.values)
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Postes_Pams(models.Model):
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'id'
|
||||||
|
REL_PAM = 'p_pam'
|
||||||
|
REL_POSTE = 'poste'
|
||||||
|
O2M_DECISION = 'decisions'
|
||||||
|
|
||||||
|
|
||||||
|
objects = BulkUpdateOrCreateQuerySet.as_manager()
|
||||||
|
|
||||||
|
id = models.CharField(primary_key=True, max_length=100)
|
||||||
|
p_pam = models.ForeignKey(PAM, on_delete = models.CASCADE, related_name='p_pam')
|
||||||
|
poste = models.ForeignKey(Poste, on_delete=models.CASCADE, related_name='poste')
|
||||||
|
p_avis_pam = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||||
|
p_avis_fe_pam = models.CharField(max_length=100, choices=AvisPosteChoices.choices, null=True, default=AvisPosteChoices.NON_ETUDIE)
|
||||||
|
p_direct_commissionne_pam = models.CharField('Direct Commissionne', max_length=100, choices=DirectCommissionneChoices.choices, null=True, blank=True)
|
||||||
|
p_notes_gestionnaire_pam = models.TextField(null=True, blank=True)
|
||||||
|
p_priorisation_pcp_pam = models.TextField('Priorisation PCP', null=True, blank=True)
|
||||||
|
info_reo = models.CharField(max_length=100, default="")
|
||||||
39
backend-django/backend/models/sous_vivier.py
Normal file
39
backend-django/backend/models/sous_vivier.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from .user import CustomUser
|
||||||
|
|
||||||
|
|
||||||
|
class SousVivier(models.Model):
|
||||||
|
"""
|
||||||
|
Modèle des sous-viviers de militaires et de postes
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
PK = 'sv_id'
|
||||||
|
LIBELLE = 'sv_libelle'
|
||||||
|
DOMAINE = 'sv_dom'
|
||||||
|
FILIERE = 'sv_fil'
|
||||||
|
CATEGORIE = 'sv_cat'
|
||||||
|
# relations many-to-many
|
||||||
|
M2M_GESTIONNAIRES = 'gestionnaires'
|
||||||
|
M2M_POSTES = 'poste'
|
||||||
|
|
||||||
|
sv_id = models.CharField(max_length=100, primary_key=True)
|
||||||
|
sv_libelle = models.CharField(max_length=100)
|
||||||
|
gestionnaires = models.ManyToManyField(CustomUser, related_name=CustomUser.Cols.M2M_SOUS_VIVIERS, blank=True)
|
||||||
|
sv_dom = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
sv_fil = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
sv_cat = models.CharField(max_length=100, null=True, blank=True)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
mails = []
|
||||||
|
for i in self.gestionnaires.all() :
|
||||||
|
mails.append(i.email)
|
||||||
|
return {
|
||||||
|
"sv_id": self.sv_id,
|
||||||
|
"sv_libelle": self.sv_libelle,
|
||||||
|
"gestionnaires": mails,
|
||||||
|
}
|
||||||
36
backend-django/backend/models/user.py
Normal file
36
backend-django/backend/models/user.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.contrib.auth.validators import UnicodeUsernameValidator
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUser(AbstractUser):
|
||||||
|
"""
|
||||||
|
Modèle de l'utilisateur de l'application
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Cols():
|
||||||
|
""" Constantes pour les noms de colonnes """
|
||||||
|
|
||||||
|
# relations one-to-many
|
||||||
|
O2M_GROUPE_FE_PCP = 'pcpfegroupe_set'
|
||||||
|
O2M_SOUS_VIVIER = 'sousvivier_set'
|
||||||
|
M2M_SOUS_VIVIERS = 'sous_vivier'
|
||||||
|
# relations many-to-many
|
||||||
|
M2M_FORMATION_EMPLOIS = 'formation_emploi'
|
||||||
|
|
||||||
|
# id = models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
|
||||||
|
id = models.IntegerField(auto_created=True, primary_key=True, verbose_name='Id SAP')
|
||||||
|
password = models.CharField(max_length=128, verbose_name='password')
|
||||||
|
last_login = models.DateTimeField(blank=True, null=True, verbose_name='last login')
|
||||||
|
is_superuser = models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')
|
||||||
|
username = models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[UnicodeUsernameValidator()], verbose_name='username')
|
||||||
|
first_name = models.CharField(null=True, blank=True, max_length=150, verbose_name='first name')
|
||||||
|
last_name = models.CharField(null=True, blank=True, max_length=150, verbose_name='last name')
|
||||||
|
email = models.EmailField(null=True, blank=True, max_length=254, verbose_name='email address')
|
||||||
|
is_staff = models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')
|
||||||
|
is_active = models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')
|
||||||
|
date_joined = models.DateTimeField(default=timezone.now, verbose_name='date joined')
|
||||||
|
grade = models.CharField(blank=True, max_length=100, null=True)
|
||||||
|
|
||||||
|
REQUIRED_FIELDS = ['id']
|
||||||
13
backend-django/backend/paginations.py
Normal file
13
backend-django/backend/paginations.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
"""Ce fichier permet de modifier la façon dont les grands ensembles de résultats sont divisés en plusieurs pages de données
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rest_framework import pagination
|
||||||
|
|
||||||
|
|
||||||
|
class HeavyDataPagination(pagination.PageNumberPagination):
|
||||||
|
"""Cette classe modifie des aspects particuliers du style de pagination, elle remplace les classes de pagination et définit les attributs à modifier.
|
||||||
|
"""
|
||||||
|
page_size = 2
|
||||||
|
page_size_query_param = 'page_size'
|
||||||
|
max_page_size = 10000
|
||||||
|
page_query_param = 'page'
|
||||||
327
backend-django/backend/reporting.py
Normal file
327
backend-django/backend/reporting.py
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
"""Ce fichier contient les utilitaires du reporting"""
|
||||||
|
# import des pré requis
|
||||||
|
from ast import For
|
||||||
|
import json
|
||||||
|
from datetime import date
|
||||||
|
from logging import getLevelName
|
||||||
|
import time
|
||||||
|
import pandas as pd
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.db.models import Sum
|
||||||
|
from .models import Administre, Domaine, FMOB, Fonction, FormationEmploi, Garnison, Grade, Poste, Notation, \
|
||||||
|
PreferencesListe, Marque, MarquesGroupe, Filiere, Decision, SousVivier, SousVivierAssociation, \
|
||||||
|
AvisPosteChoices as AvisPoste
|
||||||
|
from .utils_insertion import insert_Notation
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def reporting_taux_armement_pcp(fe_id, f_id, d_id, nf, categorie):
|
||||||
|
"""
|
||||||
|
Renvoie les indicateurs figurant dans la vue Taux Armement FE pour les PCP
|
||||||
|
|
||||||
|
:type fe_id: list
|
||||||
|
:param fe_id: codes CREDO des FE sur lesquelles on filtre
|
||||||
|
|
||||||
|
:type f_id: list
|
||||||
|
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||||
|
|
||||||
|
:type d_id: list
|
||||||
|
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||||
|
|
||||||
|
:type nf: list
|
||||||
|
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||||
|
|
||||||
|
:type categorie: list
|
||||||
|
:param categorie: catégorie sur lesquelles on filtre
|
||||||
|
|
||||||
|
|
||||||
|
:return: - **nb_militaires_actuel** (*int*): Nombre de militaires présents dans la/les FE selon les filtres cochés.
|
||||||
|
- **nb_postes_actuel** (*int*): Nombre de postes présents dans la/les FE selon les filtres cochés.
|
||||||
|
- **ecart_actuel** (*int*): Différence entre le nombre de postes et de militaires présents actuellement.
|
||||||
|
- **taux_armement_actuel** (*int*): Taux d'armement de la/les FE selon les filtres cochés.
|
||||||
|
- **nb_militaires_entrants** (*int*): Nombre de militaires entrants dans la FE selon les filtres cochés.
|
||||||
|
- **nb_militaires_sortants** (*int*): Nombre de militaires sortants de la FE selon les filtres cochés.
|
||||||
|
- **nb_militaires_projete** (*int*): Nombre de militaires attendus dans la FE après les mobilités selon les filtres cochés.
|
||||||
|
- **taux_armement_projete** (*int*): Taux d'armement projete selon les filtres cochés.
|
||||||
|
- **taux_armement_cible** (*int*): Taux d'armement cible pour la FE choisie selon la catégorie choisie, None si plusieurs catégories ont été sélectionnées.
|
||||||
|
|
||||||
|
"""
|
||||||
|
taux_armement_cible = None
|
||||||
|
if len(fe_id) == 1 and len(categorie) == 1:
|
||||||
|
fe = FormationEmploi.objects.get(fe_code__in=fe_id)
|
||||||
|
if 'MDR' in categorie:
|
||||||
|
taux_armement_cible = fe.fe_taux_armement_cible_mdr
|
||||||
|
if 'OFF' in categorie:
|
||||||
|
taux_armement_cible = fe.fe_taux_armement_cible_off
|
||||||
|
if 'SOFF' in categorie:
|
||||||
|
taux_armement_cible = fe.fe_taux_armement_cible_soff
|
||||||
|
|
||||||
|
nb_militaires_actuel = Administre.objects.filter(formation_emploi_id__in=fe_id, a_domaine__in=d_id, a_filiere__in=f_id, a_nf__in=nf, a_categorie__in=categorie).count() or 0
|
||||||
|
nb_militaires_sortants = Administre.objects.filter(a_domaine_id__in=d_id, a_filiere_id__in=f_id, a_nf__in=nf, a_categorie__in=categorie, formation_emploi_id__in=fe_id, a_statut_pam__in=['A_MUTER', 'NON_DISPONIBLE', 'PARTANT']).count() or 0
|
||||||
|
nb_militaires_entrants = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__formation_emploi_id__in=fe_id).count() or 0
|
||||||
|
nb_militaires_projete = nb_militaires_actuel + nb_militaires_entrants - nb_militaires_sortants
|
||||||
|
nb_postes_actuel = Poste.objects.filter(formation_emploi_id__in=fe_id, p_filiere__in=f_id, p_domaine__in=d_id, p_nf__in=nf, p_categorie__in=categorie).count() or 0
|
||||||
|
if nb_postes_actuel == 0:
|
||||||
|
nb_postes_actuel = 0
|
||||||
|
taux_armement_actuel = 0
|
||||||
|
taux_armement_projete = 0
|
||||||
|
else:
|
||||||
|
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||||
|
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||||
|
taux_armement_actuel = nb_militaires_actuel / nb_postes_actuel * 100
|
||||||
|
taux_armement_projete = nb_militaires_projete / nb_postes_actuel * 100
|
||||||
|
|
||||||
|
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||||
|
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||||
|
|
||||||
|
return nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible
|
||||||
|
|
||||||
|
def reporting_taux_armement_gestionnaire(fe_id, f_id, d_id, nf, categorie, sv_id):
|
||||||
|
"""
|
||||||
|
Renvoie les indicateurs figurant dans la vue Taux Armement FE pour les gestionnaires
|
||||||
|
|
||||||
|
:type fe_id: list
|
||||||
|
:param fe_id: codes CREDO des FE sur lesquelles on filtre
|
||||||
|
|
||||||
|
:type f_id: list
|
||||||
|
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||||
|
|
||||||
|
:type d_id: list
|
||||||
|
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||||
|
|
||||||
|
:type nf: list
|
||||||
|
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||||
|
|
||||||
|
:type sv_id: chaine de caractères
|
||||||
|
:param categorie: sous-vivier du gestionnaire
|
||||||
|
|
||||||
|
:type categorie: list
|
||||||
|
:param categorie: catégorie sur lesquelles on filtre
|
||||||
|
|
||||||
|
|
||||||
|
:return: - **nb_militaires_actuel** (*int*): Nombre de militaires présents dans la/les FE selon les filtres cochés.
|
||||||
|
- **nb_postes_actuel** (*int*): Nombre de postes présents dans la/les FE selon les filtres cochés.
|
||||||
|
- **ecart_actuel** (*int*): Différence entre le nombre de postes et de militaires présents actuellement.
|
||||||
|
- **taux_armement_actuel** (*int*): Taux d'armement de la/les FE selon les filtres cochés.
|
||||||
|
- **nb_militaires_entrants** (*int*): Nombre de militaires entrants dans la FE selon les filtres cochés.
|
||||||
|
- **nb_militaires_sortants** (*int*): Nombre de militaires sortants de la FE selon les filtres cochés.
|
||||||
|
- **nb_militaires_projete** (*int*): Nombre de militaires attendus dans la FE après les mobilités selon les filtres cochés.
|
||||||
|
- **taux_armement_projete** (*int*): Taux d'armement projete selon les filtres cochés.
|
||||||
|
- **taux_armement_cible** (*int*): Taux d'armement cible pour la FE choisie selon la catégorie choisie, None si plusieurs catégories ont été sélectionnées.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
taux_armement_cible = None
|
||||||
|
if len(fe_id) == 1 and len(categorie) == 1:
|
||||||
|
fe = FormationEmploi.objects.get(fe_code__in=fe_id)
|
||||||
|
if 'MDR' in categorie:
|
||||||
|
taux_armement_cible = fe.fe_taux_armement_cible_mdr
|
||||||
|
if 'OFF' in categorie:
|
||||||
|
taux_armement_cible = fe.fe_taux_armement_cible_off
|
||||||
|
if 'SOFF' in categorie:
|
||||||
|
taux_armement_cible = fe.fe_taux_armement_cible_soff
|
||||||
|
|
||||||
|
nb_militaires_actuel = Administre.objects.filter(sous_vivier_id=sv_id, formation_emploi_id__in=fe_id, a_domaine__in=d_id, a_filiere__in=f_id, a_nf__in=nf, a_categorie__in=categorie).count() or 0
|
||||||
|
nb_militaires_sortants = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_id__in=d_id, a_filiere_id__in=f_id, a_nf__in=nf, a_categorie__in=categorie, formation_emploi_id__in=fe_id, a_statut_pam__in=['A_MUTER', 'NON_DISPONIBLE', 'PARTANT']).count() or 0
|
||||||
|
nb_militaires_entrants = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__formation_emploi_id__in=fe_id).count() or 0
|
||||||
|
nb_militaires_projete = nb_militaires_actuel + nb_militaires_entrants - nb_militaires_sortants
|
||||||
|
nb_postes_actuel = Poste.objects.filter(sous_viviers=sv_id, formation_emploi_id__in=fe_id, p_filiere__in=f_id, p_domaine__in=d_id, p_nf__in=nf, p_categorie__in=categorie).count() or 0
|
||||||
|
if nb_postes_actuel == 0:
|
||||||
|
nb_postes_actuel = 0
|
||||||
|
taux_armement_actuel = 0
|
||||||
|
taux_armement_projete = 0
|
||||||
|
else:
|
||||||
|
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||||
|
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||||
|
taux_armement_actuel = nb_militaires_actuel / nb_postes_actuel * 100
|
||||||
|
taux_armement_projete = nb_militaires_projete / nb_postes_actuel * 100
|
||||||
|
|
||||||
|
ecart_actuel = nb_postes_actuel - nb_militaires_actuel
|
||||||
|
ecart_projete = nb_postes_actuel - nb_militaires_projete
|
||||||
|
|
||||||
|
return nb_militaires_actuel, nb_postes_actuel, ecart_actuel, taux_armement_actuel, nb_militaires_entrants, nb_militaires_sortants, nb_militaires_projete, taux_armement_projete, taux_armement_cible
|
||||||
|
|
||||||
|
|
||||||
|
def reporting_suivi_pam_admin(sv_id, f_id, d_id, nf, categorie):
|
||||||
|
"""
|
||||||
|
Renvoie les indicateurs figurant dans la vue Suivi PAM gestionnaire pour les administrés
|
||||||
|
|
||||||
|
:type f_id: list
|
||||||
|
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||||
|
|
||||||
|
:type d_id: list
|
||||||
|
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||||
|
|
||||||
|
:type nf: list
|
||||||
|
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||||
|
|
||||||
|
:type sv_id: chaine de caractères
|
||||||
|
:param categorie: sous-vivier du gestionnaire
|
||||||
|
|
||||||
|
:type categorie: list
|
||||||
|
:param categorie: catégorie sur lesquelles on filtre
|
||||||
|
|
||||||
|
|
||||||
|
:return: - **nb_a_etudier** (*int*): Nombre de militaires à étudier selon les filtres cochés.
|
||||||
|
- **nb_a_muter** (*int*): Nombre de militaires à muter selon les filtres cochés.
|
||||||
|
- **nb_a_maintenir** (*int*): Nombre de militaires à maintenir selon les filtres cochés.
|
||||||
|
- **nb_non_etudie_administres** (*int*): Nombre de militaires non étudiés selon les filtres cochés.
|
||||||
|
- **nb_a_partant** (*int*): Nombre de militaires partant selon les filtres cochés.
|
||||||
|
- **nb_a_non_dispo** (*int*): Nombre de militaires non disponibles selon les filtres cochés.
|
||||||
|
- **nb_prepos_administres** (*int*): Nombre de militaires prépositionnés selon les filtres cochés.
|
||||||
|
- **nb_pos_administres** (*int*): Nombre de militaires positionnés selon les filtres cochés.
|
||||||
|
- **nb_omi_active_administres** (*int*): Nombre de militaires avec un OMI activé selon les filtres cochés.
|
||||||
|
- **nb_omi_en_cours_administres** (*int*): Nombre de militaires avec un OMI en cours selon les filtres cochés.
|
||||||
|
- **reste_a_realiser_administres** (*int*): Nombre de militaires à muter ou à étudier pour lesquels aucune décision n'a été prise.
|
||||||
|
- **reste_a_realiser_a_etudier** (*int*): Nombre de militaires à étudier pour lesquels aucune décision n'a été prise.
|
||||||
|
- **reste_a_realiser_a_muter** (*int*): Nombre de militaires à muter pour lesquels aucune décision n'a été prise.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
nb_militaires_fe = Administre.objects.filter(a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie).count()
|
||||||
|
|
||||||
|
a_etudier = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="A_ETUDIER")
|
||||||
|
a_muter = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="A_MUTER")
|
||||||
|
a_maintenir = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="A_MAINTENIR")
|
||||||
|
non_etudie_administres = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="NON_ETUDIE")
|
||||||
|
a_partant = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="PARTANT")
|
||||||
|
a_non_dispo = Administre.objects.filter(sous_vivier_id=sv_id, a_domaine_futur_id__in=d_id, a_filiere_futur_id__in=f_id, a_nf_futur__in=nf, a_categorie__in=categorie, a_statut_pam="NON_DISPONIBLE")
|
||||||
|
nb_a_etudier = a_etudier.count() or 0
|
||||||
|
nb_a_muter = a_muter.count() or 0
|
||||||
|
nb_a_maintenir = a_maintenir.count() or 0
|
||||||
|
nb_non_etudie_administres = non_etudie_administres.count() or 0
|
||||||
|
nb_a_partant = a_partant.count() or 0
|
||||||
|
nb_a_non_dispo = a_non_dispo.count() or 0
|
||||||
|
nb_prepos_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='PREPOSITIONNE').count() or 0
|
||||||
|
nb_pos_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='POSITIONNE').count() or 0
|
||||||
|
nb_omi_active_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='OMI_ACTIVE').count() or 0
|
||||||
|
nb_omi_en_cours_administres = Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam__in=["A_ETUDIER", "A_MUTER"], de_decision='OMI_EN_COURS').count() or 0
|
||||||
|
reste_a_realiser_a_etudier = nb_a_etudier - Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam="A_ETUDIER").count() or 0
|
||||||
|
reste_a_realiser_a_muter = nb_a_muter - Decision.objects.filter(administre__a_domaine_futur_id__in=d_id, administre__a_filiere_futur_id__in=f_id, administre__a_nf_futur__in=nf, administre__a_categorie__in=categorie, administre__sous_vivier_id=sv_id, administre__a_statut_pam="A_MUTER").count() or 0
|
||||||
|
reste_a_realiser_administres = (nb_a_etudier + nb_a_muter) - (nb_prepos_administres + nb_pos_administres + nb_omi_en_cours_administres + nb_omi_active_administres)
|
||||||
|
|
||||||
|
return nb_a_etudier, nb_a_muter, nb_a_maintenir, nb_non_etudie_administres, nb_a_partant, nb_a_non_dispo, nb_prepos_administres, nb_pos_administres, nb_omi_active_administres, nb_omi_en_cours_administres, reste_a_realiser_administres, reste_a_realiser_a_etudier, reste_a_realiser_a_muter
|
||||||
|
|
||||||
|
|
||||||
|
def reporting_suivi_pam_poste(sv_id, f_id, d_id, nf, categorie):
|
||||||
|
"""
|
||||||
|
Renvoie les indicateurs figurant dans la vue Suivi PAM gestionnaire pour les postes
|
||||||
|
|
||||||
|
:type f_id: list
|
||||||
|
:param f_id: trigrammes des filières sur lesquelles on filtre
|
||||||
|
|
||||||
|
:type d_id: list
|
||||||
|
:param d_id: trigrammes des domaines sur lesquels on filtre
|
||||||
|
|
||||||
|
:type nf: list
|
||||||
|
:param nf: niveau fonctionnel sur lesquels on filtre
|
||||||
|
|
||||||
|
:type sv_id: chaine de caractères
|
||||||
|
:param categorie: sous-vivier du gestionnaire
|
||||||
|
|
||||||
|
:type categorie: list
|
||||||
|
:param categorie: catégorie sur lesquelles on filtre
|
||||||
|
|
||||||
|
:return: - **nb_p1** (*int*): Nombre de postes P1 selon les filtres cochés.
|
||||||
|
- **nb_p2** (*int*): Nombre de postes P2 selon les filtres cochés.
|
||||||
|
- **nb_p3** (*int*): Nombre de postes P3 selon les filtres cochés.
|
||||||
|
- **nb_p4** (*int*): Nombre de postes P4 selon les filtres cochés.
|
||||||
|
- **nb_gele** (*int*): Nombre de postes gelés selon les filtres cochés.
|
||||||
|
- **nb_non_etudie_postes** (*int*): Nombre de postes non étudiés selon les filtres cochés.
|
||||||
|
- **nb_prepos_postes** (*int*): Nombre de postes prépositionnés selon les filtres cochés.
|
||||||
|
- **nb_pos_postes** (*int*): Nombre de postes positionnés selon les filtres cochés.
|
||||||
|
- **nb_omi_active_postes** (*int*): Nombre de postes avec un OMI activé selon les filtres cochés.
|
||||||
|
- **nb_omi_en_cours_postes** (*int*): Nombre de postes avec un OMI en cours selon les filtres cochés.
|
||||||
|
- **reste_a_realiser_postes** (*int*): Nombre de postes de P1 à P4 pour lesquels aucune décision n'a été prise.
|
||||||
|
- **reste_a_realiser_p1** (*int*): Nombre de postes P1 pour lesquels aucune décision n'a été prise.
|
||||||
|
- **reste_a_realiser_p2** (*int*): Nombre de postes P2 pour lesquels aucune décision n'a été prise.
|
||||||
|
- **reste_a_realiser_p3** (*int*): Nombre de postes P3 pour lesquels aucune décision n'a été prise.
|
||||||
|
- **reste_a_realiser_p4** (*int*): Nombre de postes P4 pour lesquels aucune décision n'a été prise.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
nb_p1 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P1).count() or 0
|
||||||
|
nb_p2 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P2).count() or 0
|
||||||
|
nb_p3 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P3).count() or 0
|
||||||
|
nb_p4 = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.P4).count() or 0
|
||||||
|
nb_gele = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.GELE).count() or 0
|
||||||
|
nb_non_etudie_postes = Poste.objects.filter(sous_viviers=sv_id, p_domaine__in=d_id, p_filiere__in=f_id, p_nf__in=nf, p_categorie__in=categorie, p_avis=AvisPoste.NON_ETUDIE).count() or 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
nb_prepos_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='PREPOSITIONNE').count() or 0
|
||||||
|
nb_pos_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='POSITIONNE').count() or 0
|
||||||
|
nb_omi_active_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='OMI_ACTIVE').count() or 0
|
||||||
|
nb_omi_en_cours_postes = Decision.objects.filter(poste__p_domaine__in=d_id, poste__p_filiere__in=f_id, poste__p_nf__in=nf, poste__p_categorie__in=categorie, poste__sous_viviers=sv_id, de_decision='OMI_EN_COURS').count() or 0
|
||||||
|
|
||||||
|
reste_a_realiser_p1 = 0
|
||||||
|
reste_a_realiser_p2 = 0
|
||||||
|
reste_a_realiser_p3 = 0
|
||||||
|
reste_a_realiser_p4 = 0
|
||||||
|
reste_a_realiser_postes = (nb_p1 + nb_p2 + nb_p3 + nb_p4) - (nb_prepos_postes + nb_pos_postes + nb_omi_active_postes + nb_omi_en_cours_postes)
|
||||||
|
if reste_a_realiser_postes > nb_p4:
|
||||||
|
reste_a_realiser_p4 = nb_p4
|
||||||
|
if reste_a_realiser_postes - nb_p4 > nb_p3:
|
||||||
|
reste_a_realiser_p3 = nb_p3
|
||||||
|
if reste_a_realiser_postes - nb_p4 - nb_p3 > nb_p2:
|
||||||
|
reste_a_realiser_p2 = nb_p2
|
||||||
|
if reste_a_realiser_postes - nb_p4 - nb_p3 - nb_p2 >= nb_p1:
|
||||||
|
reste_a_realiser_p1 = nb_p1
|
||||||
|
else:
|
||||||
|
reste_a_realiser_p1 = reste_a_realiser_postes - nb_p4 - nb_p3 - nb_p2
|
||||||
|
else:
|
||||||
|
reste_a_realiser_p2 = reste_a_realiser_postes - nb_p4 - nb_p3
|
||||||
|
reste_a_realiser_p1 = 0
|
||||||
|
else:
|
||||||
|
reste_a_realiser_p3 = reste_a_realiser_postes - nb_p4
|
||||||
|
reste_a_realiser_p2 = 0
|
||||||
|
reste_a_realiser_p1 = 0
|
||||||
|
else:
|
||||||
|
reste_a_realiser_p4 = nb_p4 - reste_a_realiser_postes
|
||||||
|
reste_a_realiser_p2 = 0
|
||||||
|
reste_a_realiser_p3 = 0
|
||||||
|
reste_a_realiser_p4 = 0
|
||||||
|
|
||||||
|
|
||||||
|
return nb_p1, nb_p2, nb_p3, nb_p4, nb_gele, nb_non_etudie_postes, reste_a_realiser_postes, reste_a_realiser_p1, reste_a_realiser_p2, reste_a_realiser_p3, reste_a_realiser_p4, nb_prepos_postes, nb_pos_postes, nb_omi_active_postes, nb_omi_en_cours_postes
|
||||||
|
|
||||||
|
# TODO : Supprimer cette fonction car non utilisée dans le code et utilise SousvivierAssociation
|
||||||
|
def poste_vacant_vivier(sv_id, fe_id):
|
||||||
|
"""
|
||||||
|
Renvoie le nombre de postes vacants dans un sous-vivier pour une FE sélectionnée
|
||||||
|
|
||||||
|
:type sv_id: chaine de caractères
|
||||||
|
:param sv_id: sous-vivier du gestionnaire
|
||||||
|
|
||||||
|
:type fe_id: chaine de caractères
|
||||||
|
:param fe_id: FE sur laquelle le gestionnaire a filtré
|
||||||
|
|
||||||
|
:return: - **1** (*int*): Nombre de postes vacants pour la FE sélectionnée.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
sva = SousVivierAssociation.objects.filter(sous_vivier_id=sv_id).first()
|
||||||
|
categorie = sva.sva_categorie
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_id)
|
||||||
|
|
||||||
|
if categorie == 'MDR':
|
||||||
|
nb_poste_reevalue_fe = fe.fe_nb_poste_reevalue_mdr or 0
|
||||||
|
nb_poste_vacant_fe = fe.fe_nb_poste_vacant_mdr or 0
|
||||||
|
if categorie == 'OFF':
|
||||||
|
nb_poste_reevalue_fe = fe.fe_nb_poste_reo_off or 0
|
||||||
|
nb_poste_vacant_fe = fe.fe_nb_poste_vacant_off or 0
|
||||||
|
if categorie == 'SOFF':
|
||||||
|
nb_poste_reevalue_fe = fe.fe_nb_poste_reevalue_soff or 0
|
||||||
|
nb_poste_vacant_fe = fe.fe_nb_poste_vacant_soff or 0
|
||||||
|
|
||||||
|
nb_poste_vivier_dans_fe = Poste.objects.filter(formation_emploi_id=fe_id, sous_viviers=sv_id).aggregate(Sum('p_id')) or 0
|
||||||
|
if nb_poste_reevalue_fe == 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
proportion = nb_poste_vivier_dans_fe / nb_poste_reevalue_fe
|
||||||
|
nb_poste_vacant_vivier = proportion * nb_poste_vacant_fe
|
||||||
|
if nb_poste_vacant_vivier > np.floor(nb_poste_vacant_vivier) + 0.5:
|
||||||
|
return np.ceil(nb_poste_vacant_vivier)
|
||||||
|
else:
|
||||||
|
return np.floor(nb_poste_vacant_vivier)
|
||||||
350
backend-django/backend/serializers.py
Normal file
350
backend-django/backend/serializers.py
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
"""Ce fichier contient des fonctions qui permettent de convertir des données complexes,
|
||||||
|
telles que des querysets et des instances de modèle, en types de données Python qui peuvent ensuite
|
||||||
|
être facilement rendus en JSON, XML ou d'autres types de contenu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from .models import Competence, PcpFeGroupe, PreferencesListe, Poste, Notation, Administre, CustomUser, Grade, MarquesGroupe, \
|
||||||
|
Garnison, \
|
||||||
|
SousVivier, Domaine, Filiere, FMOB, Fonction, FormationEmploi, Marque, Decision
|
||||||
|
|
||||||
|
from . import constants
|
||||||
|
|
||||||
|
|
||||||
|
class FileSVSerializer(serializers.Serializer):
|
||||||
|
"""Cette classe ne sera utilisée que pour valider si le fichier SV est un vrai fichier.
|
||||||
|
"""
|
||||||
|
SV = serializers.FileField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['sv_file']
|
||||||
|
|
||||||
|
|
||||||
|
class ScoringValidator(serializers.Serializer):
|
||||||
|
""" pour interprêter et valider le contenu JSON """
|
||||||
|
sous_vivier_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||||
|
|
||||||
|
class FileCompetenceSerializer(serializers.Serializer):
|
||||||
|
"""Cette classe ne sera utilisée que pour valider si les fichiers competence_v1 et competence_v2 sont un vrais fichier.
|
||||||
|
"""
|
||||||
|
competence_v1 = serializers.FileField()
|
||||||
|
competence_v2 = serializers.FileField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['competence_file_v1', 'competence_file_v2']
|
||||||
|
|
||||||
|
|
||||||
|
class FileSerializer(serializers.Serializer):
|
||||||
|
"""Cette classe ne sera utilisée que pour valider si les fichiers Donnees_BO_ADT, REO, ZPROPAF, FMOB, domaine_filiere, insee_mapin et diplomes sont des vrais fichiers.
|
||||||
|
"""
|
||||||
|
Donnees_BO_ADT = serializers.FileField(help_text="Fichier BO ADT 1.1")
|
||||||
|
REO = serializers.FileField()
|
||||||
|
ZPROPAF = serializers.FileField()
|
||||||
|
FMOB = serializers.FileField()
|
||||||
|
domaine_filiere = serializers.FileField()
|
||||||
|
insee_maping = serializers.FileField()
|
||||||
|
diplomes = serializers.FileField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['Donnees_BO_ADT', 'REO', 'ZPROPAF', 'FMOB', 'domaine_filiere', 'insee_maping', 'diplomes']
|
||||||
|
|
||||||
|
class AlimentationReferentielSerializer(serializers.Serializer):
|
||||||
|
""" Valide que les données sont bien des fichiers. """
|
||||||
|
|
||||||
|
referentiel_fe = serializers.FileField(help_text="Référentiel FE")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['referentiel_fe']
|
||||||
|
|
||||||
|
|
||||||
|
class NotationSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets Notation en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Notation
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class GestionnaireSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets customusers en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = CustomUser
|
||||||
|
fields = ['grade', 'first_name', 'last_name', 'email']
|
||||||
|
|
||||||
|
|
||||||
|
class PreferencesListeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets preferencesliste en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = PreferencesListe
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class FmobSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets Fmob en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = FMOB
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleDecisionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets decision en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class AdministreDecisionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets decisions en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Et à l'ajout des variables poste_fe_code, poste_fe_libelle, poste_fe_garnison liés à la decision au json.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
poste_fe_code = serializers.CharField(source='poste.formation_emploi.fe_code', read_only=True)
|
||||||
|
poste_fe_libelle = serializers.CharField(source='poste.formation_emploi.fe_libelle', read_only=True)
|
||||||
|
poste_fe_garnison = serializers.CharField(source='poste.formation_emploi.garnison.gar_lieu', read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class MarquesGroupeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets marquegroupes en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = MarquesGroupe
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class MarqueSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets marque en type json contenant les informations de marque et les champs de marquesgroupe liés à chaque marque.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
groupe_marques = MarquesGroupeSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Marque
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class DomaineSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets domaines en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Domaine
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class FiliereSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets filières en type json contenant les informations de filière
|
||||||
|
et les champs du domaine liés à chaque filiere.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
domaine = DomaineSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Filiere
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class GradeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets grades en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Grade
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class GarnisonSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets garnisons en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Garnison
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class FormationEmploiSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets FormationEmploi en type json contenant les informations de FormationEmploi
|
||||||
|
et le champs garnison lieu liés à chaque FormationEmploi.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class MereSerializer(serializers.ModelSerializer):
|
||||||
|
"""Classe de représentation de la FE mère"""
|
||||||
|
fe_code = serializers.ReadOnlyField()
|
||||||
|
fe_libelle = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FormationEmploi
|
||||||
|
fields = ['fe_code', 'fe_libelle']
|
||||||
|
|
||||||
|
|
||||||
|
garnison = serializers.CharField(source=f'{FormationEmploi.Cols.REL_GARNISON}.gar_lieu')
|
||||||
|
mere = MereSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FormationEmploi
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class FonctionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets fonctions en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Fonction
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SousVivierSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets SousVivier en type json contenant les informations du SousVivier
|
||||||
|
et la/le gestionnaire responsable de chaque SousVivier.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
gestionnaire = GestionnaireSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SousVivier
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SousVivierAssociationSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la creation d'un json contenant les champs du SousVivier
|
||||||
|
et les champs de filière liés à chaque SousVivier.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
filiere = FiliereSerializer()
|
||||||
|
sous_vivier = SousVivierSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class CompetenceSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets competences en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Competence
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class PosteSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets postes en type json contenant les champs du poste
|
||||||
|
et les champs de fonction, formation_emploi, sous_vivier et decisions liés à chaque poste. Cette classe va également ordonner le json par p_id et valider certaines variables.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
fonction = FonctionSerializer()
|
||||||
|
formation_emploi = FormationEmploiSerializer()
|
||||||
|
sous_vivier = SousVivierSerializer(allow_null=True)
|
||||||
|
sous_vivier_id = serializers.IntegerField(write_only=True, allow_null=True)
|
||||||
|
decisions = SimpleDecisionSerializer(many=True)
|
||||||
|
p_nb_prepositionne = serializers.IntegerField(read_only=True)
|
||||||
|
p_nb_positionne = serializers.IntegerField(read_only=True)
|
||||||
|
p_nb_omi_en_cours = serializers.IntegerField(read_only=True)
|
||||||
|
p_nb_omi_active = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Poste
|
||||||
|
ordering = ['p_id']
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AdministreSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets administres en type json contenant les champs de l'administré
|
||||||
|
et les champs de fonction, formation_emploi, sous_vivier, decisions, grade et Fmob liés à chaque administré. Cette classe va également ordonner le json par a_id_sap.
|
||||||
|
Les variables qui seront affichées dans le json sont celles mentionnées dans la variable fields.
|
||||||
|
"""
|
||||||
|
a_id_sap = serializers.ReadOnlyField()
|
||||||
|
formation_emploi = FormationEmploiSerializer(read_only=True)
|
||||||
|
sous_vivier = SousVivierSerializer(read_only=True)
|
||||||
|
sous_vivier_id = serializers.IntegerField(write_only=True, allow_null=True)
|
||||||
|
fonction = FonctionSerializer(read_only=True)
|
||||||
|
grade = GradeSerializer(read_only=True)
|
||||||
|
decision = AdministreDecisionSerializer(read_only=True)
|
||||||
|
fmobs = FmobSerializer(many=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Administre
|
||||||
|
ordering = ['a_id_sap']
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
"""
|
||||||
|
fields = ["a_id_sap",
|
||||||
|
"formation_emploi",
|
||||||
|
"formation_emploi_id",
|
||||||
|
"fonction",
|
||||||
|
"fonction_id",
|
||||||
|
"sous_vivier",
|
||||||
|
"sous_vivier_id",
|
||||||
|
"grade",
|
||||||
|
"decision",
|
||||||
|
"fmobs",
|
||||||
|
"a_liste_id_marques",
|
||||||
|
"a_liste_id_competences",
|
||||||
|
"a_nom",
|
||||||
|
"a_prenom",
|
||||||
|
"a_sexe",
|
||||||
|
"a_fonction",
|
||||||
|
"a_code_fonction",
|
||||||
|
"a_domaine",
|
||||||
|
"a_filiere",
|
||||||
|
"a_nf",
|
||||||
|
"a_categorie",
|
||||||
|
"a_domaine_futur",
|
||||||
|
"a_filiere_futur",
|
||||||
|
"a_nf_futur",
|
||||||
|
"a_bureau_gestion",
|
||||||
|
"a_date_entree_service",
|
||||||
|
"a_arme",
|
||||||
|
"a_rg_origine_recrutement",
|
||||||
|
"a_date_naissance",
|
||||||
|
"a_liste_depts_souhaites",
|
||||||
|
"a_interruption_service",
|
||||||
|
"a_situation_fam",
|
||||||
|
"a_date_rdc",
|
||||||
|
"a_date_dernier_acr",
|
||||||
|
"a_sap_conjoint",
|
||||||
|
"a_statut_pam",
|
||||||
|
"a_notes_gestionnaire",
|
||||||
|
"a_notes_partagees"]
|
||||||
|
read_only_fields = ("a_id_sap",)
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DecisionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets decisions en type json contenant les champs de decision
|
||||||
|
et les champs de notation, poste et administre liés à chaque decision . Cette classe va également ordonner le json par de_date_decision.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
notation = NotationSerializer()
|
||||||
|
poste = PosteSerializer()
|
||||||
|
administre = AdministreSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = '__all__'
|
||||||
|
ordering = ['de_date_decision']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PcpFeGroupeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets PcpFeGroupe en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = PcpFeGroupe
|
||||||
|
fields = '__all__'
|
||||||
17
backend-django/backend/serializers/__init__.py
Normal file
17
backend-django/backend/serializers/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from .administre import *
|
||||||
|
from .alimentation import *
|
||||||
|
from .commun import *
|
||||||
|
from .current_user import *
|
||||||
|
from .decision import *
|
||||||
|
from .domaine import *
|
||||||
|
from .exportation_fichiers import *
|
||||||
|
from .filiere import *
|
||||||
|
from .fmob import *
|
||||||
|
from .fonction import *
|
||||||
|
from .formation_emploi import *
|
||||||
|
from .grade import *
|
||||||
|
from .initial import *
|
||||||
|
from .notation import *
|
||||||
|
from .pcp_fe_groupe import *
|
||||||
|
from .sous_vivier import *
|
||||||
|
from .suppression import *
|
||||||
41
backend-django/backend/serializers/administre.py
Normal file
41
backend-django/backend/serializers/administre.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
from ..models import (Administre, Decision, DecisionChoices, DecisionTree,
|
||||||
|
FormationEmploi, Poste, FMOB, Administres_Pams)
|
||||||
|
from ..utils.attributes import safe_rgetattr
|
||||||
|
from ..utils.decisions import KEY_CREATE, KEY_UPDATE, get_available_decisions
|
||||||
|
from ..utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||||
|
get_profiles_by_adm)
|
||||||
|
from .commun import AssignmentState
|
||||||
|
from .fmob import FmobSerializer
|
||||||
|
from .fonction import FonctionSerializer
|
||||||
|
from .formation_emploi import FormationEmploiSerializer
|
||||||
|
from .grade import GradeSerializer
|
||||||
|
from .sous_vivier import SousVivierSerializer
|
||||||
|
|
||||||
|
CTX_KEY_DECISIONS = 'decisions'
|
||||||
|
CTX_KEY_PROFILES = 'profiles'
|
||||||
|
|
||||||
|
|
||||||
|
class AdministreSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Cette classe sera responsable de la conversion des objets administres en type json contenant les champs de l'administré
|
||||||
|
et les champs de fonction, formation_emploi, sous_vivier, decisions, grade et Fmob liés à chaque administré. Cette classe va également ordonner le json par a_id_sap.
|
||||||
|
Les variables qui seront affichées dans le json sont celles mentionnées dans la variable fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
a_id_sap = serializers.ReadOnlyField()
|
||||||
|
formation_emploi = FormationEmploiSerializer(read_only=True)
|
||||||
|
sous_vivier = SousVivierSerializer(read_only=True)
|
||||||
|
sous_vivier_id = serializers.IntegerField(write_only=True, allow_null=True)
|
||||||
|
fonction = FonctionSerializer(read_only=True)
|
||||||
|
grade = GradeSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Administre
|
||||||
|
ordering = [Administre.Cols.PK]
|
||||||
|
fields = '__all__'
|
||||||
86
backend-django/backend/serializers/alimentation.py
Normal file
86
backend-django/backend/serializers/alimentation.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class FileSVSerializer(serializers.Serializer):
|
||||||
|
"""Cette classe ne sera utilisée que pour valider si le fichier SV est un vrai fichier.
|
||||||
|
"""
|
||||||
|
SV = serializers.FileField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = ['sv_file']
|
||||||
|
|
||||||
|
|
||||||
|
class ChargementCompetencesSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Cette classe ne sera utilisée que pour valider si les fichiers competence_v1 et competence_v2 sont un vrais fichier.
|
||||||
|
"""
|
||||||
|
ref_skills = serializers.FileField(label="Référentiel des compétences", help_text="Fichier 'Référenciel des compétences_corrigé v2.xlsx'", required = False)
|
||||||
|
specific_skills = serializers.FileField(label="Compétences particulières", help_text="Fichier 'COMPETENCES PARTICULIERES.xlsx'", required = False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class AlimentationSerializer(serializers.Serializer):
|
||||||
|
"""Cette classe ne sera utilisée que pour valider si les fichiers Donnees_BO_ADT, REO, Ref Gest, Ref Org, Ref Sv Fil, Ref FE, FMOB, domaine_filiere, insee_mapin et diplomes sont des vrais fichiers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Donnees_BO_ADT = serializers.FileField(label="Données BO", help_text="Fichier 'Données BO_Anonymes.xlsx'", required=False)
|
||||||
|
REO = serializers.FileField(label="REO", help_text="Fichier 'REO2022_OGURE_NG.xlsx'", required=False)
|
||||||
|
REO_PAM_SUIVANT = serializers.FileField(label="REO A + 1",help_text="Fichier REO2023_OGURE_NG.xlsx", required=False)
|
||||||
|
REO_OCV = serializers.FileField(label="Requêtes OCV", help_text="Fichier 'Copie requete ocv postes et mise à poste v2.xlsx'", required=False)
|
||||||
|
referentiel_gestionnaire = serializers.FileField(label="Référentiel de gestionnaires", help_text="Fichier 'Référentiel gestionnaires DRHAT.xlsx'", required=False)
|
||||||
|
referentiel_organique = serializers.FileField(label="Référentiel organique", help_text="Fichier 'Référentiel organique DRHAT.xlsx'", required=False)
|
||||||
|
refeferentiel_sous_vivier_filiere = serializers.FileField(label="Référentiel de sous-viviers/filières", help_text="Fichier 'Référentiel sous-viviers filières_V3.xlsx'", required=False)
|
||||||
|
referentiel_fe = serializers.FileField(label="Référentiel FE", help_text="Fichier 'Référentiel FE anonymisé_V2.xlsx'", required=False)
|
||||||
|
FMOB = serializers.FileField(label="Formulaire de mobilité", help_text="Fichier 'FMOB_FEMP modifié.xlsx'", required=False)
|
||||||
|
FMOB_PAM_SUIVANT = serializers.FileField(label="Formulaire de mobilité A + 1", help_text="Fichier 'FMOB PAM A1 (2023).xlsx", required=False)
|
||||||
|
domaine_filiere = serializers.FileField(label="Domaines - filières", help_text="Fichier 'DOMAINES FILIERES DRHAT.xlsx'", required=False)
|
||||||
|
insee_maping = serializers.FileField(label="INSEE", help_text="Fichier 'INSEE.xlsx'", required=False)
|
||||||
|
diplomes = serializers.FileField(label="Diplômes", help_text="Fichier 'Diplomes 2021.xlsx'", required=False)
|
||||||
|
FUD = serializers.FileField(label="FUD", help_text="Fichier 'FUD.xlsx'", required=False)
|
||||||
|
ref_zones_geo = serializers.FileField(label="Référentiel des zones géographiques", help_text="Fichier 'Zones_Geographique.xlsx'", required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AlimentationReferentielSerializer(serializers.Serializer):
|
||||||
|
""" Valide que les données sont bien des fichiers. """
|
||||||
|
|
||||||
|
referentiel_fe = serializers.FileField(label='Référentiel FE', help_text='Référentiel FE anonymisé_V2.xlsx')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class AlimentationZoneGeographiqueSerializer(serializers.Serializer):
|
||||||
|
""" Valide que les données sont bien des fichiers. """
|
||||||
|
|
||||||
|
ref_zones_geo = serializers.FileField(label="Référentiel des zones géographiques", help_text="Fichier 'Zones_Geographique.xlsx'", required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class AlimentationCommentairesSerializer(serializers.Serializer):
|
||||||
|
""" Valide que les données sont bien des fichiers. """
|
||||||
|
|
||||||
|
commentaires = serializers.FileField(label='Commentaires', help_text='20220427_NP_DRHAT_SDG_BCCM_com-OGURE.xlsx', required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class AlimentationRefsDroitSerializer(serializers.Serializer):
|
||||||
|
""" Valide que les données sont bien des fichiers. """
|
||||||
|
|
||||||
|
referentiel_gestionnaire = serializers.FileField(label="Référentiel de gestionnaires", help_text="Fichier 'Référentiel gestionnaires DRHAT.xlsx'", required=False)
|
||||||
|
referentiel_organique = serializers.FileField(label="Référentiel organique", help_text="Fichier 'Référentiel organique DRHAT.xlsx'", required=False)
|
||||||
|
refeferentiel_sous_vivier_filiere = serializers.FileField(label="Référentiel de sous-viviers/filières", help_text="Fichier 'Référentiel sous-viviers filières_V3.xlsx'", required=False)
|
||||||
|
referentiel_fe = serializers.FileField(label="Référentiel FE", help_text="Fichier 'Référentiel FE anonymisé_V2.xlsx'", required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
29
backend-django/backend/serializers/commun.py
Normal file
29
backend-django/backend/serializers/commun.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
|
||||||
|
class AssignmentState(str, Enum):
|
||||||
|
""" état d'affectation BMOB (lié au changement de BMOB) """
|
||||||
|
|
||||||
|
# reste dans la même FE
|
||||||
|
RESTANT = 'RESTANT'
|
||||||
|
|
||||||
|
# entre dans une FE du périmètre du PCP
|
||||||
|
ENTRANT = 'ENTRANT'
|
||||||
|
|
||||||
|
# sort d'une FE du périmètre du PCP
|
||||||
|
SORTANT = 'SORTANT'
|
||||||
|
|
||||||
|
# ENTRANT + SORTANT (mais pas RESTANT car ce n'est pas la même FE)
|
||||||
|
ENTRANT_SORTANT = 'ENTRANT_SORTANT'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s.%s" % (self.__class__.__name__, self._name_)
|
||||||
|
|
||||||
|
|
||||||
|
class ChoicesSerializer(serializers.Serializer):
|
||||||
|
""" Représente un élément qui hérite de Choices sous forme {code, libelle} """
|
||||||
|
|
||||||
|
code = serializers.ReadOnlyField(source='value')
|
||||||
|
libelle = serializers.ReadOnlyField(source='label')
|
||||||
72
backend-django/backend/serializers/current_user.py
Normal file
72
backend-django/backend/serializers/current_user.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import CustomUser, SousVivier, FormationEmploi
|
||||||
|
from ..utils.permissions import KEY_READ, KEY_WRITE
|
||||||
|
from .pcp_fe_groupe import PcpFeGroupeSerializer
|
||||||
|
|
||||||
|
CTX_KEY_SOUS_VIVIERS_SUPER = 'sous_viviers_super'
|
||||||
|
CTX_KEY_SOUS_VIVIERS = 'sous_viviers'
|
||||||
|
CTX_KEY_FORMATION_EMPLOIS = 'formation_emplois'
|
||||||
|
CTX_KEY_PCP_FE_GROUPE = 'fe_groupe'
|
||||||
|
CTX_KEY_PROFILES = 'profiles'
|
||||||
|
|
||||||
|
|
||||||
|
class _SousVivierUserSerializer(serializers.ModelSerializer):
|
||||||
|
""" Classe de représentation d'un sous-vivier de l'utilisateur """
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SousVivier
|
||||||
|
exclude = ('gestionnaires',)
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
res = super().to_representation(instance)
|
||||||
|
ids_super = self.context.get(CTX_KEY_SOUS_VIVIERS_SUPER)
|
||||||
|
if ids_super and instance.pk in ids_super:
|
||||||
|
res['admin'] = True
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class _FormationEmploiUserSerializer(serializers.ModelSerializer):
|
||||||
|
""" Classe de représentation d'un sous-vivier de l'utilisateur """
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FormationEmploi
|
||||||
|
fields = [FormationEmploi.Cols.PK, FormationEmploi.Cols.LIBELLE, 'fe_mere_credo', 'fe_mere_la', 'gestionnaire_id']
|
||||||
|
|
||||||
|
gestionnaire_id = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
res = super().to_representation(instance)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfoSerializer(serializers.ModelSerializer):
|
||||||
|
""" Gère la forme publique de l'utilisateur connecté. """
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CustomUser
|
||||||
|
fields = ['id', 'username', 'first_name', 'last_name', 'email', 'grade', 'administre']
|
||||||
|
|
||||||
|
administre = serializers.ReadOnlyField(source='administre_id')
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
res = super().to_representation(instance)
|
||||||
|
|
||||||
|
svs_super = self.context.get(CTX_KEY_SOUS_VIVIERS_SUPER) or ()
|
||||||
|
svs = self.context.get(CTX_KEY_SOUS_VIVIERS) or ()
|
||||||
|
fes = self.context.get(CTX_KEY_FORMATION_EMPLOIS) or ()
|
||||||
|
all_svs = set(list(svs) + list(svs_super))
|
||||||
|
all_fes = set(list(fes))
|
||||||
|
svs_ids_specific_super = {sv.pk for sv in svs_super if sv not in svs}
|
||||||
|
|
||||||
|
res['sous_viviers'] = _SousVivierUserSerializer(all_svs, many=True, context={
|
||||||
|
CTX_KEY_SOUS_VIVIERS_SUPER: svs_ids_specific_super
|
||||||
|
}).data
|
||||||
|
|
||||||
|
res['formation_emplois'] = _FormationEmploiUserSerializer(all_fes, many=True).data
|
||||||
|
|
||||||
|
res['fe_groupe'] = PcpFeGroupeSerializer(self.context.get(CTX_KEY_PCP_FE_GROUPE) or ()).data
|
||||||
|
|
||||||
|
profiles = self.context.get(CTX_KEY_PROFILES) or {}
|
||||||
|
res['profils'] = {'lecture': profiles.get(KEY_READ) or (), 'ecriture': profiles.get(KEY_WRITE) or ()}
|
||||||
|
return res
|
||||||
35
backend-django/backend/serializers/decision.py
Normal file
35
backend-django/backend/serializers/decision.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import Decision, DecisionChoices
|
||||||
|
from .administre import AdministreSerializer
|
||||||
|
from .initial import PosteSerializer
|
||||||
|
from .notation import NotationSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class DecisionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets decisions en type json contenant les champs de decision
|
||||||
|
et les champs de notation, poste et administre liés à chaque decision . Cette classe va également ordonner le json par de_date_decision.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
notation = NotationSerializer()
|
||||||
|
# poste = PosteSerializer()
|
||||||
|
# administre = AdministreSerializer()
|
||||||
|
de_date_decision = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = '__all__'
|
||||||
|
ordering = ['de_date_decision']
|
||||||
|
|
||||||
|
|
||||||
|
class CreateDecisionSerializer(serializers.ModelSerializer):
|
||||||
|
""" Valide les données pour une création de décision """
|
||||||
|
|
||||||
|
administre_id = serializers.IntegerField(write_only=True)
|
||||||
|
poste_id = serializers.CharField(max_length=100, write_only=True)
|
||||||
|
de_decision = serializers.ChoiceField(choices=DecisionChoices.choices)
|
||||||
|
delete_former = serializers.BooleanField(write_only=True, required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = ['administre_id', 'poste_id', 'de_decision', 'delete_former']
|
||||||
12
backend-django/backend/serializers/domaine.py
Normal file
12
backend-django/backend/serializers/domaine.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models.domaine import Domaine
|
||||||
|
|
||||||
|
|
||||||
|
class DomaineSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets domaines en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Domaine
|
||||||
|
fields = '__all__'
|
||||||
11
backend-django/backend/serializers/exportation_fichiers.py
Normal file
11
backend-django/backend/serializers/exportation_fichiers.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from tabnanny import verbose
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
class ExportationSerializer(serializers.Serializer):
|
||||||
|
"""Cette classe ne sera utilisée que pour valider si les fichiers Donnees_BO_ADT, REO, Ref Gest, Ref Org, Ref Sv Fil, Ref FE, FMOB, domaine_filiere, insee_mapin et diplomes sont des vrais fichiers.
|
||||||
|
"""
|
||||||
|
FICHIERS_CHOICES = (("1", "Données BO"), ("2", "REO"),)
|
||||||
|
fichier_exporte = serializers.ChoiceField(label='Fichier à exporter', help_text='Selectionnez le fichier que vous souhaitez exporter', choices=FICHIERS_CHOICES)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
16
backend-django/backend/serializers/filiere.py
Normal file
16
backend-django/backend/serializers/filiere.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models.filiere import Filiere
|
||||||
|
from .domaine import DomaineSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FiliereSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets filières en type json contenant les informations de filière
|
||||||
|
et les champs du domaine liés à chaque filiere.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
domaine = DomaineSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Filiere
|
||||||
|
fields = '__all__'
|
||||||
12
backend-django/backend/serializers/fmob.py
Normal file
12
backend-django/backend/serializers/fmob.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import FMOB
|
||||||
|
|
||||||
|
|
||||||
|
class FmobSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets Fmob en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = FMOB
|
||||||
|
fields = '__all__'
|
||||||
12
backend-django/backend/serializers/fonction.py
Normal file
12
backend-django/backend/serializers/fonction.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import Fonction
|
||||||
|
|
||||||
|
|
||||||
|
class FonctionSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets fonctions en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Fonction
|
||||||
|
fields = '__all__'
|
||||||
25
backend-django/backend/serializers/formation_emploi.py
Normal file
25
backend-django/backend/serializers/formation_emploi.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from ..models import FormationEmploi, Garnison
|
||||||
|
|
||||||
|
|
||||||
|
class FormationEmploiSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets FormationEmploi en type json contenant les informations de FormationEmploi
|
||||||
|
et le champs garnison lieu liés à chaque FormationEmploi.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class MereSerializer(serializers.ModelSerializer):
|
||||||
|
"""Classe de représentation de la FE mère"""
|
||||||
|
fe_code = serializers.ReadOnlyField()
|
||||||
|
fe_libelle = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FormationEmploi
|
||||||
|
fields = ['fe_code', 'fe_libelle']
|
||||||
|
|
||||||
|
|
||||||
|
mere = MereSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FormationEmploi
|
||||||
|
exclude = ('gestionnaires',)
|
||||||
12
backend-django/backend/serializers/grade.py
Normal file
12
backend-django/backend/serializers/grade.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import Grade
|
||||||
|
|
||||||
|
|
||||||
|
class GradeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets grades en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Grade
|
||||||
|
fields = '__all__'
|
||||||
259
backend-django/backend/serializers/initial.py
Normal file
259
backend-django/backend/serializers/initial.py
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
"""Ce fichier contient des fonctions qui permettent de convertir des données complexes,
|
||||||
|
telles que des querysets et des instances de modèle, en types de données Python qui peuvent ensuite
|
||||||
|
être facilement rendus en JSON, XML ou d'autres types de contenu.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rest_framework import serializers
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
from .. import constants
|
||||||
|
from ..models import (Competence, Decision, Garnison, Marque, MarquesGroupe,
|
||||||
|
Poste, PAM, Administres_Pams, FormationEmploi, Postes_Pams,
|
||||||
|
DecisionChoices, DecisionTree, FMOB)
|
||||||
|
|
||||||
|
from .commun import AssignmentState
|
||||||
|
from ..utils.attributes import safe_rgetattr
|
||||||
|
from ..utils.decisions import KEY_CREATE, KEY_UPDATE, get_available_decisions
|
||||||
|
from ..utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||||
|
get_profiles_by_adm)
|
||||||
|
from .commun import ChoicesSerializer
|
||||||
|
from .filiere import FiliereSerializer
|
||||||
|
from .fonction import FonctionSerializer
|
||||||
|
from .formation_emploi import FormationEmploiSerializer
|
||||||
|
from .sous_vivier import SousVivierSerializer
|
||||||
|
from .fmob import FmobSerializer
|
||||||
|
from .administre import AdministreSerializer
|
||||||
|
|
||||||
|
|
||||||
|
CTX_KEY_DECISIONS = 'decisions'
|
||||||
|
CTX_KEY_PROFILES = 'profiles'
|
||||||
|
|
||||||
|
|
||||||
|
class ScoringValidator(serializers.Serializer):
|
||||||
|
""" pour interprêter et valider le contenu JSON """
|
||||||
|
sous_vivier_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||||
|
pam_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||||
|
|
||||||
|
class ScoringSelectifValidator(serializers.Serializer):
|
||||||
|
""" pour interprêter et valider le contenu JSON """
|
||||||
|
sous_vivier_id = serializers.CharField(max_length=100, required=True, allow_null=False, allow_blank=False)
|
||||||
|
administre_id = serializers.ListField()
|
||||||
|
poste_id = serializers.ListField()
|
||||||
|
pam_id = serializers.CharField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleDecisionPosteSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets decision en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
grade = serializers.ReadOnlyField(source='administre.grade.gr_code')
|
||||||
|
nom = serializers.ReadOnlyField(source='administre.a_nom')
|
||||||
|
prenom = serializers.ReadOnlyField(source='administre.a_prenom')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class AdministresPamsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class DecisionLocalSerializer(serializers.ModelSerializer):
|
||||||
|
"""Classe de représentation locale d'une décision"""
|
||||||
|
|
||||||
|
class PosteLocalSerializer(serializers.Serializer):
|
||||||
|
"""Classe de représentation locale d'un poste"""
|
||||||
|
|
||||||
|
class FormationEmploiLocalSerializer(FormationEmploiSerializer):
|
||||||
|
"""Classe de représentation locale d'une FE"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = FormationEmploi
|
||||||
|
fields = ['fe_code', 'fe_libelle', 'fe_mere_credo', 'fe_mere_la', 'fe_garnison_lieu', 'mere']
|
||||||
|
|
||||||
|
p_id = serializers.ReadOnlyField()
|
||||||
|
formation_emploi = FormationEmploiLocalSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Poste
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
poste = PosteLocalSerializer(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Decision
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
administre = AdministreSerializer()
|
||||||
|
pam_id = serializers.ReadOnlyField(source='pam.pam_id')
|
||||||
|
pam_date = serializers.ReadOnlyField(source='pam.pam_date')
|
||||||
|
pam_libelle = serializers.ReadOnlyField(source='pam.pam_libelle')
|
||||||
|
pam_statut = serializers.ReadOnlyField(source='pam.pam_statut')
|
||||||
|
fmobs = FmobSerializer()
|
||||||
|
decision = DecisionLocalSerializer()
|
||||||
|
profils = serializers.SerializerMethodField()
|
||||||
|
decisions_possibles = serializers.SerializerMethodField()
|
||||||
|
etat_fe_bmob = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_decisions_possibles(self, obj: Administres_Pams) -> Tuple[DecisionChoices]:
|
||||||
|
decisions_by_adm = self.context.get(CTX_KEY_DECISIONS) or get_available_decisions((obj,), user=self.context['request'].user)
|
||||||
|
decisions = decisions_by_adm.get(obj.id) or {}
|
||||||
|
return {'creation': decisions.get(KEY_CREATE) or (), 'maj': decisions.get(KEY_UPDATE) or ()}
|
||||||
|
|
||||||
|
def get_profils(self, obj: Administres_Pams) -> Tuple[Profiles]:
|
||||||
|
profiles_by_adm = self.context.get(CTX_KEY_PROFILES) or get_profiles_by_adm(self.context['request'].user, obj)
|
||||||
|
profiles = profiles_by_adm.get(obj.id) or {}
|
||||||
|
return {'lecture': profiles.get(KEY_READ) or (), 'ecriture': profiles.get(KEY_WRITE) or ()}
|
||||||
|
|
||||||
|
def get_etat_fe_bmob(self, obj: Administres_Pams) -> Optional[AssignmentState]:
|
||||||
|
decision = safe_rgetattr(obj, f'{Administres_Pams.Cols.REL_DECISION}.{Decision.Cols.STATUT}')
|
||||||
|
if not decision or DecisionTree.ME not in DecisionChoices(decision).trees:
|
||||||
|
return None
|
||||||
|
|
||||||
|
profiles_by_adm = self.context.get(CTX_KEY_PROFILES) or get_profiles_by_adm(self.context['request'].user, obj)
|
||||||
|
profiles = (profiles_by_adm.get(obj.id) or {}).get(KEY_READ) or ()
|
||||||
|
if Profiles.PCP in profiles:
|
||||||
|
return AssignmentState.RESTANT
|
||||||
|
|
||||||
|
current = Profiles.PCP_ACTUEL in profiles
|
||||||
|
future = Profiles.PCP_FUTUR in profiles
|
||||||
|
if current and future:
|
||||||
|
return AssignmentState.ENTRANT_SORTANT
|
||||||
|
if current:
|
||||||
|
return AssignmentState.SORTANT
|
||||||
|
if future:
|
||||||
|
return AssignmentState.ENTRANT
|
||||||
|
return None
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
|
||||||
|
ret.update(ret.pop('administre'))
|
||||||
|
ret['a_statut_pam'] = ret.pop('a_statut_pam_annee')
|
||||||
|
|
||||||
|
if 'fmobs' in ret and ret['fmobs']:
|
||||||
|
ret['fmobs'] = [ret['fmobs']]
|
||||||
|
|
||||||
|
return ret
|
||||||
|
class Meta:
|
||||||
|
model = Administres_Pams
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class PAMSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets decision en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = PAM
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class MarquesGroupeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets marquegroupes en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = MarquesGroupe
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class MarqueSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets marque en type json contenant les informations de marque et les champs de marquesgroupe liés à chaque marque.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
groupe_marques = MarquesGroupeSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Marque
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class GarnisonSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets garnisons en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Garnison
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class SousVivierAssociationSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la creation d'un json contenant les champs du SousVivier
|
||||||
|
et les champs de filière liés à chaque SousVivier.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
filiere = FiliereSerializer()
|
||||||
|
sous_vivier = SousVivierSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
class CompetenceSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets competences en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Competence
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PosteSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets postes en type json contenant les champs du poste
|
||||||
|
et les champs de fonction, formation_emploi, sous_vivier et decisions liés à chaque poste. Cette classe va également ordonner le json par p_id et valider certaines variables.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
p_id = serializers.ReadOnlyField()
|
||||||
|
fonction = FonctionSerializer()
|
||||||
|
formation_emploi = FormationEmploiSerializer()
|
||||||
|
sous_viviers = SousVivierSerializer(many=True)
|
||||||
|
p_nb_prepositionne = serializers.IntegerField(read_only=True)
|
||||||
|
p_nb_positionne = serializers.IntegerField(read_only=True)
|
||||||
|
p_nb_omi_en_cours = serializers.IntegerField(read_only=True)
|
||||||
|
p_nb_omi_active = serializers.IntegerField(read_only=True)
|
||||||
|
p_poids_competences = serializers.IntegerField(read_only=True)
|
||||||
|
p_poids_filiere = serializers.IntegerField(read_only=True)
|
||||||
|
p_poids_nf = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Poste
|
||||||
|
ordering = ['p_id']
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
class PostesPamsSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
pam_id = serializers.ReadOnlyField(source='p_pam.pam_id')
|
||||||
|
pam_date = serializers.ReadOnlyField(source='p_pam.pam_date')
|
||||||
|
pam_libelle = serializers.ReadOnlyField(source='p_pam.pam_libelle')
|
||||||
|
pam_statut = serializers.ReadOnlyField(source='p_pam.pam_statut')
|
||||||
|
decisions = SimpleDecisionPosteSerializer()
|
||||||
|
poste = PosteSerializer()
|
||||||
|
|
||||||
|
|
||||||
|
def to_representation(self, instance):
|
||||||
|
ret = super().to_representation(instance)
|
||||||
|
ret.update(ret.pop('poste'))
|
||||||
|
|
||||||
|
if ret['decisions']:
|
||||||
|
ret['decisions'] = [ret['decisions']]
|
||||||
|
else :
|
||||||
|
ret['decisions'] = []
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Postes_Pams
|
||||||
|
fields = '__all__'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class RefStatutPamChoicesSerializer(ChoicesSerializer):
|
||||||
|
""" Représente un élément StatutPamChoices dans les données de référence """
|
||||||
|
|
||||||
|
inclusAlgo = serializers.ReadOnlyField(source='calc_enabled')
|
||||||
|
affectationManuellePossible = serializers.ReadOnlyField(source='dec_enabled')
|
||||||
|
|
||||||
|
|
||||||
|
class RefAvisPosteChoicesSerializer(RefStatutPamChoicesSerializer):
|
||||||
|
""" Représente un élément AvisPosteChoices dans les données de référence """
|
||||||
11
backend-django/backend/serializers/notation.py
Normal file
11
backend-django/backend/serializers/notation.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from ..models import Notation
|
||||||
|
|
||||||
|
|
||||||
|
class NotationSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets Notation en type json contenant uniquement les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = Notation
|
||||||
|
fields = '__all__'
|
||||||
12
backend-django/backend/serializers/pcp_fe_groupe.py
Normal file
12
backend-django/backend/serializers/pcp_fe_groupe.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import PcpFeGroupe
|
||||||
|
|
||||||
|
|
||||||
|
class PcpFeGroupeSerializer(serializers.ModelSerializer):
|
||||||
|
"""Cette classe sera responsable de la conversion des objets PcpFeGroupe en type json contenant les informations mentionnées dans la variable fields.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = PcpFeGroupe
|
||||||
|
fields = '__all__'
|
||||||
15
backend-django/backend/serializers/sous_vivier.py
Normal file
15
backend-django/backend/serializers/sous_vivier.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from ..models import SousVivier
|
||||||
|
|
||||||
|
|
||||||
|
class SousVivierSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Cette classe sera responsable de la conversion des objets SousVivier en type json contenant les informations du SousVivier
|
||||||
|
et la/le gestionnaire responsable de chaque SousVivier.
|
||||||
|
Si fields='__all__', alors toutes les variables liées à cette classe seront affichées dans le json.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SousVivier
|
||||||
|
exclude = (SousVivier.Cols.M2M_GESTIONNAIRES,)
|
||||||
9
backend-django/backend/serializers/suppression.py
Normal file
9
backend-django/backend/serializers/suppression.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
class SuppressionAdministresSerializer(serializers.Serializer):
|
||||||
|
""" Valide que les données sont bien des fichiers. """
|
||||||
|
|
||||||
|
administres = serializers.FileField(label='Administrés à supprimer', help_text="Fichier 'Militaires à supprimer d\'OGURE.xlsx'", required=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
fields = '__all__'
|
||||||
198
backend-django/backend/signals.py
Normal file
198
backend-django/backend/signals.py
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
"""
|
||||||
|
Les signaux permettent à certains expéditeurs d’avertir un ensemble de destinataires qu’une action a eu lieu.
|
||||||
|
Dans ce projet, les signaux ont été utilisés pour mettre à jour les compteurs des postes appartenant à la FE lorsque le taux d'armement change en fonction de la catégorie.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from . import constants
|
||||||
|
from .models import FormationEmploi, Poste, SousVivierAssociation, Administre
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def update_taux_mdr(sender, instance, **kwargs):
|
||||||
|
"""Met à jour les compteurs des postes appartenant à la FE dont on change le taux d'armement cible pour la catégorie MDR
|
||||||
|
|
||||||
|
:type instance: objet du modèle FormationEmploi
|
||||||
|
:param instance: FE dont on change le taux d'armement cible pour la catégorie MDR
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
taux = instance.fe_taux_armement_cible_mdr
|
||||||
|
fe_id = instance.fe_code
|
||||||
|
|
||||||
|
postes = Poste.objects.filter(formation_emploi_id=fe_id, p_categorie='MDR')
|
||||||
|
if (len(postes) > 0) and (taux is not None):
|
||||||
|
taux = taux * 0.01
|
||||||
|
for poste in postes:
|
||||||
|
new_nb_reevalue = poste.p_nb_reevalue * taux
|
||||||
|
new_nb_vacant = new_nb_reevalue - poste.p_nb_occupe
|
||||||
|
if new_nb_reevalue >= poste.p_nb_reevalue:
|
||||||
|
new_nb_p4 = poste.p_nb_p4 + new_nb_vacant - poste.p_nb_vacant
|
||||||
|
poste.p_nb_p4 = new_nb_p4
|
||||||
|
|
||||||
|
else:
|
||||||
|
if new_nb_reevalue > poste.p_nb_occupe:
|
||||||
|
new_nb_non_etudie = poste.p_nb_non_etudie + poste.p_nb_reevalue - new_nb_reevalue
|
||||||
|
poste.p_nb_non_etudie = new_nb_non_etudie
|
||||||
|
delta_vacant = new_nb_vacant - poste.p_nb_vacant
|
||||||
|
if delta_vacant <= poste.p_nb_p4:
|
||||||
|
new_poste_p4 = poste.p_nb_p4 - delta_vacant
|
||||||
|
poste.p_nb_p4 = new_poste_p4
|
||||||
|
else:
|
||||||
|
delta_vacant -= poste.p_nb_p4
|
||||||
|
poste.p_nb_p4 = 0
|
||||||
|
if delta_vacant <= poste.p_nb_p3:
|
||||||
|
new_poste_p3 = poste.p_nb_p3 - delta_vacant
|
||||||
|
poste.p_nb_p3 = new_poste_p3
|
||||||
|
else:
|
||||||
|
delta_vacant -= poste.p_nb_p3
|
||||||
|
poste.p_nb_p3 = 0
|
||||||
|
if delta_vacant <= poste.p_nb_p2:
|
||||||
|
new_poste_p2 = poste.p_nb_p2 - delta_vacant
|
||||||
|
poste.p_nb_p2 = new_poste_p2
|
||||||
|
else:
|
||||||
|
delta_vacant -= poste.p_nb_p2
|
||||||
|
poste.p_nb_p2 = 0
|
||||||
|
new_nb_p1 = poste.p_nb_p1 - delta_vacant
|
||||||
|
poste.p_nb_p1 = new_nb_p1
|
||||||
|
else:
|
||||||
|
poste.p_nb_p3 = 0
|
||||||
|
poste.p_nb_p2 = 0
|
||||||
|
poste.p_nb_p1 = 0
|
||||||
|
poste.p_nb_p4 = 0
|
||||||
|
|
||||||
|
poste.p_nb_reevalue = new_nb_reevalue
|
||||||
|
poste.p_nb_vacant = new_nb_vacant
|
||||||
|
poste.save(
|
||||||
|
update_fields=['p_nb_reevalue', 'p_nb_vacant', 'p_nb_non_etudie', 'p_nb_p4', 'p_nb_p3', 'p_nb_p2',
|
||||||
|
'p_nb_p1'])
|
||||||
|
|
||||||
|
|
||||||
|
post_save.connect(update_taux_mdr, sender=FormationEmploi)
|
||||||
|
|
||||||
|
|
||||||
|
def update_taux_off(sender, instance, **kwargs):
|
||||||
|
"""Met à jour les compteurs des postes appartenant à la FE dont on change le taux d'armement cible pour la catégorie OFF
|
||||||
|
|
||||||
|
:type instance: objet du modèle FormationEmploi
|
||||||
|
:param instance: FE dont on change le taux d'armement cible pour la catégorie OFF
|
||||||
|
|
||||||
|
"""
|
||||||
|
taux = instance.fe_taux_armement_cible_off
|
||||||
|
fe_id = instance.fe_code
|
||||||
|
|
||||||
|
postes = Poste.objects.filter(formation_emploi_id=fe_id, p_categorie='OFF')
|
||||||
|
if (len(postes) > 0) and (taux is not None):
|
||||||
|
taux = taux * 0.01
|
||||||
|
for poste in postes:
|
||||||
|
new_nb_reevalue = poste.p_nb_reevalue * taux
|
||||||
|
new_nb_vacant = new_nb_reevalue - poste.p_nb_occupe
|
||||||
|
if new_nb_reevalue >= poste.p_nb_reevalue:
|
||||||
|
new_nb_p4 = poste.p_nb_p4 + new_nb_vacant - poste.p_nb_vacant
|
||||||
|
poste.p_nb_p4 = new_nb_p4
|
||||||
|
|
||||||
|
else:
|
||||||
|
if new_nb_reevalue > poste.p_nb_occupe:
|
||||||
|
new_nb_non_etudie = poste.p_nb_non_etudie + poste.p_nb_reevalue - new_nb_reevalue
|
||||||
|
poste.p_nb_non_etudie = new_nb_non_etudie
|
||||||
|
delta_vacant = new_nb_vacant - poste.p_nb_vacant
|
||||||
|
if delta_vacant <= poste.p_nb_p4:
|
||||||
|
new_poste_p4 = poste.p_nb_p4 - delta_vacant
|
||||||
|
poste.p_nb_p4 = new_poste_p4
|
||||||
|
else:
|
||||||
|
delta_vacant -= poste.p_nb_p4
|
||||||
|
poste.p_nb_p4 = 0
|
||||||
|
if delta_vacant <= poste.p_nb_p3:
|
||||||
|
new_poste_p3 = poste.p_nb_p3 - delta_vacant
|
||||||
|
poste.p_nb_p3 = new_poste_p3
|
||||||
|
else:
|
||||||
|
delta_vacant -= poste.p_nb_p3
|
||||||
|
poste.p_nb_p3 = 0
|
||||||
|
if delta_vacant <= poste.p_nb_p2:
|
||||||
|
new_poste_p2 = poste.p_nb_p2 - delta_vacant
|
||||||
|
poste.p_nb_p2 = new_poste_p2
|
||||||
|
else:
|
||||||
|
delta_vacant -= poste.p_nb_p2
|
||||||
|
poste.p_nb_p2 = 0
|
||||||
|
new_nb_p1 = poste.p_nb_p1 - delta_vacant
|
||||||
|
poste.p_nb_p1 = new_nb_p1
|
||||||
|
else:
|
||||||
|
poste.p_nb_p3 = 0
|
||||||
|
poste.p_nb_p2 = 0
|
||||||
|
poste.p_nb_p1 = 0
|
||||||
|
poste.p_nb_p4 = 0
|
||||||
|
|
||||||
|
poste.p_nb_reevalue = new_nb_reevalue
|
||||||
|
poste.p_nb_vacant = new_nb_vacant
|
||||||
|
poste.save(
|
||||||
|
update_fields=['p_nb_reevalue', 'p_nb_vacant', 'p_nb_non_etudie', 'p_nb_p4', 'p_nb_p3', 'p_nb_p2',
|
||||||
|
'p_nb_p1'])
|
||||||
|
|
||||||
|
|
||||||
|
post_save.connect(update_taux_off, sender=FormationEmploi)
|
||||||
|
|
||||||
|
|
||||||
|
def update_taux_soff(sender, instance, **kwargs):
|
||||||
|
"""Met à jour les compteurs des postes appartenant à la FE dont on change le taux d'armement cible pour la catégorie SOFF
|
||||||
|
|
||||||
|
:type instance: objet du modèle FormationEmploi
|
||||||
|
:param instance: FE dont on change le taux d'armement cible pour la catégorie SOFF
|
||||||
|
|
||||||
|
"""
|
||||||
|
taux = instance.fe_taux_armement_cible_soff
|
||||||
|
fe_id = instance.fe_code
|
||||||
|
|
||||||
|
postes = Poste.objects.filter(formation_emploi_id=fe_id, p_categorie='SOFF')
|
||||||
|
if (len(postes) > 0) and (taux is not None):
|
||||||
|
taux = taux * 0.01
|
||||||
|
|
||||||
|
for poste in postes:
|
||||||
|
new_nb_reevalue = np.ceil(poste.p_nb_reo * taux)
|
||||||
|
if new_nb_reevalue != poste.p_nb_reevalue:
|
||||||
|
if new_nb_reevalue > poste.p_nb_reevalue:
|
||||||
|
new_nb_p4 = poste.p_nb_p4 + new_nb_reevalue - poste.p_nb_reevalue
|
||||||
|
new_nb_non_etudie = (new_nb_reevalue - new_nb_p4) if (new_nb_reevalue - new_nb_p4) > 0 else 0
|
||||||
|
|
||||||
|
elif new_nb_reevalue > poste.p_nb_occupe:
|
||||||
|
new_nb_p4 = new_nb_reevalue - poste.p_nb_occupe
|
||||||
|
new_nb_non_etudie = new_nb_reevalue - new_nb_p4
|
||||||
|
poste.p_nb_p3 = 0
|
||||||
|
poste.p_nb_p2 = 0
|
||||||
|
poste.p_nb_p1 = 0
|
||||||
|
else:
|
||||||
|
new_nb_p4 = 0
|
||||||
|
new_nb_non_etudie = 0
|
||||||
|
poste.p_nb_p3 = 0
|
||||||
|
poste.p_nb_p2 = 0
|
||||||
|
poste.p_nb_p1 = 0
|
||||||
|
poste.p_nb_p4 = 0
|
||||||
|
new_nb_reevalue = poste.p_nb_occupe
|
||||||
|
|
||||||
|
poste.p_nb_non_etudie = new_nb_non_etudie
|
||||||
|
poste.p_nb_p4 = new_nb_p4
|
||||||
|
poste.p_nb_reevalue = new_nb_reevalue
|
||||||
|
poste.save(
|
||||||
|
update_fields=['p_nb_reevalue', 'p_nb_non_etudie', 'p_nb_p4', 'p_nb_p3', 'p_nb_p2',
|
||||||
|
'p_nb_p1'])
|
||||||
|
|
||||||
|
|
||||||
|
post_save.connect(update_taux_soff, sender=FormationEmploi)
|
||||||
|
|
||||||
|
|
||||||
|
def add_sva(sender, instance, **kwargs):
|
||||||
|
"""Met à jour les sous-viviers des militaires ayant la même filière de la nouvelle instance de SousVivierAssociation créée
|
||||||
|
|
||||||
|
:param instance: La nouvelle instance de SousVivierAssociation créée
|
||||||
|
:type instance: objet du modèle SousVivierAssociation
|
||||||
|
|
||||||
|
"""
|
||||||
|
filiere_id = instance.filiere_id
|
||||||
|
sva_categorie = instance.sva_categorie
|
||||||
|
sous_vivier_id = instance.sous_vivier_id
|
||||||
|
administres = Administre.objects.filter(a_filiere_id=filiere_id, a_categorie=sva_categorie)
|
||||||
|
administres.update(sous_vivier_id=sous_vivier_id)
|
||||||
|
|
||||||
|
|
||||||
|
post_save.connect(add_sva, sender=SousVivierAssociation)
|
||||||
25
backend-django/backend/templates/admin/groupe_fe.html
Normal file
25
backend-django/backend/templates/admin/groupe_fe.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
Entrer le nom du groupe des formations d'emploi
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{% for fe in formations_emplois %}
|
||||||
|
<li>
|
||||||
|
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% for obj in formations_emplois %}
|
||||||
|
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||||
|
{% endfor %}
|
||||||
|
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||||
|
{{ form.nom_groupe_fe }}
|
||||||
|
<br/>
|
||||||
|
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||||
|
<input type="submit" name="apply" value="Modifier le nom du groupe"/>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
25
backend-django/backend/templates/admin/taux_cible_mdr.html
Normal file
25
backend-django/backend/templates/admin/taux_cible_mdr.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
Entrer le nouveau taux cible d'armement pour la catégorie MDR pour les formations-emplois sélectionnées
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{% for fe in formations_emplois %}
|
||||||
|
<li>
|
||||||
|
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% for obj in formations_emplois %}
|
||||||
|
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||||
|
{% endfor %}
|
||||||
|
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||||
|
{{ form.taux_armement_cible }}
|
||||||
|
<br/>
|
||||||
|
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||||
|
<input type="submit" name="apply" value="Modifier le taux cible"/>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
25
backend-django/backend/templates/admin/taux_cible_off.html
Normal file
25
backend-django/backend/templates/admin/taux_cible_off.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
Entrer le nouveau taux cible d'armement pour la catégorie OFF pour les formations-emplois sélectionnées
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{% for fe in formations_emplois %}
|
||||||
|
<li>
|
||||||
|
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% for obj in formations_emplois %}
|
||||||
|
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||||
|
{% endfor %}
|
||||||
|
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||||
|
{{ form.taux_armement_cible }}
|
||||||
|
<br/>
|
||||||
|
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||||
|
<input type="submit" name="apply" value="Modifier le taux cible"/>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
25
backend-django/backend/templates/admin/taux_cible_soff.html
Normal file
25
backend-django/backend/templates/admin/taux_cible_soff.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>
|
||||||
|
Entrer le nouveau taux cible d'armement pour la catégorie SOFF pour les formations-emplois sélectionnées
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
{% for fe in formations_emplois %}
|
||||||
|
<li>
|
||||||
|
{{ fe.fe_code }} — {{ fe.fe_libelle }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% for obj in formations_emplois %}
|
||||||
|
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">
|
||||||
|
{% endfor %}
|
||||||
|
{# <input type="text" name="taux_armement_cli" value="POMME"/>#}
|
||||||
|
{{ form.taux_armement_cible }}
|
||||||
|
<br/>
|
||||||
|
<input type="hidden" name="action" value="{{ do_action }}"/>
|
||||||
|
<input type="submit" name="apply" value="Modifier le taux cible"/>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
137
backend-django/backend/tests.py
Normal file
137
backend-django/backend/tests.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
"""
|
||||||
|
Ce fichier n'est utilisé que pour les tests.
|
||||||
|
"""
|
||||||
|
from django.test import TestCase
|
||||||
|
from numpy.core.fromnumeric import shape
|
||||||
|
import pandas as pd
|
||||||
|
from .utils_scoring import encoding,notePonderee,notations_test
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
|
||||||
|
class InsertModelTest(TestCase):
|
||||||
|
# Fonction pour la table Garnisons
|
||||||
|
def test_scoring(self):
|
||||||
|
"""Pour tester la fonction de scoring
|
||||||
|
"""
|
||||||
|
mut_test = notations_test()
|
||||||
|
data_test = mut_test.to_dict('records')
|
||||||
|
data_source = [{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 1,
|
||||||
|
'administres_id_sap': 9984,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.5,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 1,
|
||||||
|
'administres_id_sap': 56754,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.0,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 1,
|
||||||
|
'administres_id_sap': 78905,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.0,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 2,
|
||||||
|
'administres_id_sap': 9984,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.0,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 2,
|
||||||
|
'administres_id_sap': 56754,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.5,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 2,
|
||||||
|
'administres_id_sap': 78905,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.0,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 3,
|
||||||
|
'administres_id_sap': 9984,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.0,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 3,
|
||||||
|
'administres_id_sap': 56754,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.0,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'mu_id': 0,
|
||||||
|
'poste_id': 3,
|
||||||
|
'administres_id_sap': 78905,
|
||||||
|
'pam_id': 1,
|
||||||
|
'mu_date_execution': '14/06/2021',
|
||||||
|
'mu_note_militaire': 0.5,
|
||||||
|
'mu_flag_cple_ideal': 0,
|
||||||
|
'mu_decision': '',
|
||||||
|
'mu_date_decision': '',
|
||||||
|
'mu_notes_gestionnaire': '',
|
||||||
|
'mu_notes_partagees': ''
|
||||||
|
}]
|
||||||
|
if data_source == data_test :
|
||||||
|
test_value = True
|
||||||
|
else :
|
||||||
|
test_value = False
|
||||||
|
self.assertIs(test_value,True)
|
||||||
5
backend-django/backend/tests/__init__.py
Normal file
5
backend-django/backend/tests/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .initial import *
|
||||||
|
from .models import *
|
||||||
|
from .utils import *
|
||||||
|
from .views import *
|
||||||
|
from .droits import *
|
||||||
1
backend-django/backend/tests/droits/__init__.py
Normal file
1
backend-django/backend/tests/droits/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .socle import *
|
||||||
590
backend-django/backend/tests/droits/socle.py
Normal file
590
backend-django/backend/tests/droits/socle.py
Normal file
@@ -0,0 +1,590 @@
|
|||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APIClient, APITestCase
|
||||||
|
|
||||||
|
from ...models import (Administre, CustomUser, Decision, DecisionChoices,
|
||||||
|
Domaine, Filiere, FormationEmploi, Garnison, Poste,
|
||||||
|
RefGest, RefOrg, RefSvFil, SousVivier)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSocleVerifDroits(APITestCase):
|
||||||
|
""" Classe permettant de tester la vérification des droits de lecture et d'écriture d'un gestionnaire """
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
|
||||||
|
# Création des gestionnaires
|
||||||
|
cls.gest1 = CustomUser.objects.create(username='gest1', password='password', id=1)
|
||||||
|
cls.gest2 = CustomUser.objects.create(username='gest2', password='password', id=2)
|
||||||
|
cls.gest3 = CustomUser.objects.create(username='gest3', password='password', id=3)
|
||||||
|
cls.gest4 = CustomUser.objects.create(username='gest4', password='password', id=4)
|
||||||
|
cls.gest5 = CustomUser.objects.create(username='gest5', password='password', id=5)
|
||||||
|
cls.gest6 = CustomUser.objects.create(username='gest6', password='password', id=6)
|
||||||
|
cls.gest7 = CustomUser.objects.create(username='gest7', password='password', id=7)
|
||||||
|
cls.gest8 = CustomUser.objects.create(username='gest8', password='password', id=8)
|
||||||
|
cls.gest9 = CustomUser.objects.create(username='gest9', password='password', id=9)
|
||||||
|
cls.gest10 = CustomUser.objects.create(username='gest10', password='password', id=10)
|
||||||
|
|
||||||
|
|
||||||
|
# Création des objets du référentiel organique
|
||||||
|
cls.ref_org30 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org30',
|
||||||
|
ref_org_code_niv_org3='org30',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_itd=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
) # Référentiel organique avec les droits de lecture et d'écriture (attribué aux gestionnaires 1)
|
||||||
|
|
||||||
|
cls.ref_org40a = RefOrg.objects.create(
|
||||||
|
ref_org_code='org40a',
|
||||||
|
ref_org_code_niv_org3='org30',
|
||||||
|
ref_org_code_niv_org4='org40a',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_itd=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.ref_org40b = RefOrg.objects.create(
|
||||||
|
ref_org_code='org40b',
|
||||||
|
ref_org_code_niv_org3='org30',
|
||||||
|
ref_org_code_niv_org4='org40b',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_itd=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.ref_org41 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org41',
|
||||||
|
ref_org_code_niv_org4='org41',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True
|
||||||
|
) # Référentiel organique sans les droits de lecture et d'écriture (attribué aux gestionnaires 3)
|
||||||
|
|
||||||
|
cls.ref_org42 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org42',
|
||||||
|
ref_org_code_niv_org4='org42',
|
||||||
|
ref_org_droit_lect=True
|
||||||
|
) # Référentiel organique avec les droits de lecture mais ni pcp ni fil ni itd (attribué aux gestionnaires 4)
|
||||||
|
|
||||||
|
cls.ref_org43 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org43',
|
||||||
|
ref_org_code_niv_org4='org43',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.ref_org44 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org44',
|
||||||
|
ref_org_code_niv_org4='org44',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_droit_lect=True
|
||||||
|
) # Référentiel organique sans les droits d'écriture (attribué aux gestionnaires 6)
|
||||||
|
|
||||||
|
cls.ref_org45 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org45',
|
||||||
|
ref_org_code_niv_org4='org45',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
) # Référentiel organique sans FE ni FIL (attribué aux gestionnaires 7)
|
||||||
|
|
||||||
|
cls.ref_org36 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org36',
|
||||||
|
ref_org_code_niv_org3='org36',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
) # Référentiel organique ne possedant aucune instances dans les ref FE et FIL (attribué aux gestionnaires 8)
|
||||||
|
|
||||||
|
cls.ref_org37 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org37',
|
||||||
|
ref_org_code_niv_org3='org37',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.ref_org47a = RefOrg.objects.create(
|
||||||
|
ref_org_code='org47a',
|
||||||
|
ref_org_code_niv_org3='org37',
|
||||||
|
ref_org_code_niv_org4='org47a',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.ref_org47b = RefOrg.objects.create(
|
||||||
|
ref_org_code='org47b',
|
||||||
|
ref_org_code_niv_org3='org37',
|
||||||
|
ref_org_code_niv_org4='org47b',
|
||||||
|
ref_org_ref_fe=True,
|
||||||
|
ref_org_ref_sv_fil=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
)
|
||||||
|
|
||||||
|
cls.ref_org48 = RefOrg.objects.create(
|
||||||
|
ref_org_code='org48',
|
||||||
|
ref_org_code_niv_org4='org48',
|
||||||
|
ref_org_itd=True,
|
||||||
|
ref_org_droit_lect=True,
|
||||||
|
ref_org_droit_ecr=True
|
||||||
|
) # Référentiel organique avec tous les droits mais seulement pour un gestionnaire ITD (attribué au gestionnaire 10)
|
||||||
|
|
||||||
|
|
||||||
|
# Création des objets du référentiel gestionnaire
|
||||||
|
cls.ref_gest1 = RefGest.objects.create(ref_gest_sap=cls.gest1.id, ref_gest_org=cls.ref_org30)
|
||||||
|
cls.ref_gest2 = RefGest.objects.create(ref_gest_sap=cls.gest2.id) # Gestionnaire ne possedant pas de cod_niv_org
|
||||||
|
cls.ref_gest3 = RefGest.objects.create(ref_gest_sap=cls.gest3.id, ref_gest_org=cls.ref_org41)
|
||||||
|
cls.ref_gest4 = RefGest.objects.create(ref_gest_sap=cls.gest4.id, ref_gest_org=cls.ref_org42)
|
||||||
|
cls.ref_gest5 = RefGest.objects.create(ref_gest_sap=cls.gest5.id, ref_gest_org=cls.ref_org43)
|
||||||
|
cls.ref_gest6 = RefGest.objects.create(ref_gest_sap=cls.gest6.id, ref_gest_org=cls.ref_org44)
|
||||||
|
cls.ref_gest7 = RefGest.objects.create(ref_gest_sap=cls.gest7.id, ref_gest_org=cls.ref_org45)
|
||||||
|
cls.ref_gest8 = RefGest.objects.create(ref_gest_sap=cls.gest8.id, ref_gest_org=cls.ref_org36)
|
||||||
|
cls.ref_gest9 = RefGest.objects.create(ref_gest_sap=cls.gest9.id, ref_gest_org=cls.ref_org37)
|
||||||
|
cls.ref_gest10 = RefGest.objects.create(ref_gest_sap=cls.gest10.id, ref_gest_org=cls.ref_org48)
|
||||||
|
|
||||||
|
# Création du référentiel FE et des DOM/FIL
|
||||||
|
cls.gar1 = Garnison.objects.create(gar_id='Gar1', gar_lieu='lieu1')
|
||||||
|
cls.fe1 = FormationEmploi.objects.create(fe_code='Fe1', fe_code_niv_org4='org40a', fe_garnison_lieu=cls.gar1.gar_lieu)
|
||||||
|
cls.fe2 = FormationEmploi.objects.create(fe_code='Fe2', fe_code_niv_org4='org43', fe_garnison_lieu=cls.gar1.gar_lieu)
|
||||||
|
cls.fe3 = FormationEmploi.objects.create(fe_code='Fe3', fe_code_niv_org4='org47a', fe_garnison_lieu=cls.gar1.gar_lieu)
|
||||||
|
|
||||||
|
cls.dom1 = Domaine.objects.create(d_code='Dom1')
|
||||||
|
cls.fil1 = Filiere.objects.create(f_code='Fil1')
|
||||||
|
|
||||||
|
|
||||||
|
# Création des administrés, postes et décisions
|
||||||
|
cls.adm1 = Administre.objects.create(a_id_sap=1, formation_emploi=cls.fe1, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip1') # Administré du fe1
|
||||||
|
cls.adm2 = Administre.objects.create(a_id_sap=2, formation_emploi=cls.fe1, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip2') # Idem
|
||||||
|
cls.adm3 = Administre.objects.create(a_id_sap=3, formation_emploi=cls.fe2, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip3') # Administré du fe2
|
||||||
|
cls.adm4 = Administre.objects.create(a_id_sap=4, formation_emploi=cls.fe2, a_domaine=cls.dom1, a_filiere=cls.fil1, a_categorie='Cat1', a_nom='nom', a_prenom='prénom', a_sexe='M', a_eip='eip4') # Idem
|
||||||
|
|
||||||
|
cls.poste1 = Poste.objects.create(p_id='Poste1', formation_emploi=cls.fe1, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip1')
|
||||||
|
cls.poste2 = Poste.objects.create(p_id='Poste2', formation_emploi=cls.fe1, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip2')
|
||||||
|
cls.poste3 = Poste.objects.create(p_id='Poste3', formation_emploi=cls.fe2, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip3')
|
||||||
|
cls.poste4 = Poste.objects.create(p_id='Poste4', formation_emploi=cls.fe2, p_domaine=cls.dom1, p_filiere=cls.fil1, p_categorie='Cat1', p_eip='eip4')
|
||||||
|
cls.poste5 = Poste.objects.create(p_id='Poste5', p_specifique='ITD', p_eip='eip5')
|
||||||
|
|
||||||
|
cls.dec1 = Decision.objects.create(administre=cls.adm1, poste=cls.poste1, de_decision=DecisionChoices.PROPOSITION_FE)
|
||||||
|
cls.dec2 = Decision.objects.create(administre=cls.adm3, poste=cls.poste3, de_decision=DecisionChoices.PROPOSITION_FE)
|
||||||
|
|
||||||
|
# Création du référentiel sous-vivier filiere
|
||||||
|
cls.ref_sv_fil1 = RefSvFil.objects.create(ref_sv_fil_code='org40b', ref_sv_fil_dom='Dom1', ref_sv_fil_fil='Fil1', ref_sv_fil_cat='Cat1')
|
||||||
|
|
||||||
|
|
||||||
|
# Création des clients de test
|
||||||
|
cls.c0 = APIClient() # Client de test qui ne sera pas authentifié
|
||||||
|
cls.c1 = APIClient()
|
||||||
|
cls.c2 = APIClient()
|
||||||
|
cls.c3 = APIClient()
|
||||||
|
cls.c4 = APIClient()
|
||||||
|
cls.c5 = APIClient()
|
||||||
|
cls.c6 = APIClient()
|
||||||
|
cls.c7 = APIClient()
|
||||||
|
cls.c8 = APIClient()
|
||||||
|
cls.c9 = APIClient()
|
||||||
|
cls.c10 = APIClient()
|
||||||
|
|
||||||
|
# Authentification des clients de test
|
||||||
|
cls.c1.force_authenticate(cls.gest1)
|
||||||
|
cls.c2.force_authenticate(cls.gest2)
|
||||||
|
cls.c3.force_authenticate(cls.gest3)
|
||||||
|
cls.c4.force_authenticate(cls.gest4)
|
||||||
|
cls.c5.force_authenticate(cls.gest5)
|
||||||
|
cls.c6.force_authenticate(cls.gest6)
|
||||||
|
cls.c7.force_authenticate(cls.gest7)
|
||||||
|
cls.c8.force_authenticate(cls.gest8)
|
||||||
|
cls.c9.force_authenticate(cls.gest9)
|
||||||
|
cls.c10.force_authenticate(cls.gest10)
|
||||||
|
|
||||||
|
cls.list_clients = [cls.c1, cls.c2, cls.c3, cls.c4, cls.c5, cls.c6, cls.c7, cls.c8, cls.c9, cls.c10]
|
||||||
|
|
||||||
|
# Création des données utilsées pour les requêtes
|
||||||
|
cls.patch_datas = {
|
||||||
|
'data_p1': {
|
||||||
|
'p_id': 'Poste1',
|
||||||
|
'p_eip': 'eip2'
|
||||||
|
},
|
||||||
|
'data_a1': {
|
||||||
|
'a_id_sap': 1,
|
||||||
|
'a_eip': 'eip2'
|
||||||
|
},
|
||||||
|
'data_d1': {
|
||||||
|
'pk': cls.adm1.pk,
|
||||||
|
'de_decision': DecisionChoices.POSITIONNE
|
||||||
|
},
|
||||||
|
'data_p3': {
|
||||||
|
'p_id': 'Poste3',
|
||||||
|
'p_eip': 'eip4'
|
||||||
|
},
|
||||||
|
'data_a3': {
|
||||||
|
'a_id_sap': 3,
|
||||||
|
'a_eip': 'eip4'
|
||||||
|
},
|
||||||
|
'data_d3': {
|
||||||
|
'pk': cls.adm3.pk,
|
||||||
|
'de_decision': DecisionChoices.POSITIONNE
|
||||||
|
},
|
||||||
|
'data_p5': {
|
||||||
|
'p_id': 'Poste5',
|
||||||
|
'p_eip': 'eip6'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cls.put_datas = {
|
||||||
|
'data_p1': [
|
||||||
|
{'p_id': 'Poste1',
|
||||||
|
'p_eip': 'eip2'
|
||||||
|
},
|
||||||
|
{'p_id': 'Poste2',
|
||||||
|
'p_eip': 'eip3'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'data_a1': [
|
||||||
|
{'a_id_sap': 1,
|
||||||
|
'a_eip': 'eip2'
|
||||||
|
},
|
||||||
|
{'a_id_sap': 2,
|
||||||
|
'a_eip': 'eip3'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'data_p3': [
|
||||||
|
{'p_id': 'Poste3',
|
||||||
|
'p_eip': 'eip4'
|
||||||
|
},
|
||||||
|
{'p_id': 'Poste4',
|
||||||
|
'p_eip': 'eip5'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'data_a3': [
|
||||||
|
{'a_id_sap': 3,
|
||||||
|
'a_eip': 'eip4'
|
||||||
|
},
|
||||||
|
{'a_id_sap': 4,
|
||||||
|
'a_eip': 'eip5'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Définition des liens sur lesquels les requêtes seront effectuées
|
||||||
|
cls.urls = {
|
||||||
|
'list_p': reverse('Poste-list'),
|
||||||
|
'list_a': reverse('Administre-list'),
|
||||||
|
'list_d': reverse('Decision-list'),
|
||||||
|
'instance_p1': reverse('Poste-list') + 'Poste1/',
|
||||||
|
'instance_a1': reverse('Administre-list') + '1/',
|
||||||
|
'instance_d1': reverse('Decision-list') + '1/',
|
||||||
|
'instance_p3': reverse('Poste-list') + 'Poste3/',
|
||||||
|
'instance_a3': reverse('Administre-list') + '3/',
|
||||||
|
'instance_d3': reverse('Decision-list') + '3/',
|
||||||
|
'instance_p5': reverse('Poste-list') + 'Poste5/'
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
super().tearDownClass()
|
||||||
|
for client in cls.list_clients:
|
||||||
|
client.logout()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_not_authenticated(self):
|
||||||
|
"""
|
||||||
|
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'est pas authentifié
|
||||||
|
"""
|
||||||
|
# Le test passe mais il y a également interdiction car c5 n'a pas de 'code_niv_org'...
|
||||||
|
get_resp_p = self.c0.get(path=self.urls['list_p'], follow=True)
|
||||||
|
get_resp_a = self.c0.get(path=self.urls['list_a'], follow=True)
|
||||||
|
get_resp_d = self.c0.get(path=self.urls['list_d'], follow=True)
|
||||||
|
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_get_ok(self):
|
||||||
|
"""
|
||||||
|
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que :
|
||||||
|
- Le gestionnaire est authentifié,
|
||||||
|
- Qu'il a les droits de lecture
|
||||||
|
"""
|
||||||
|
get_resp_p = self.c1.get(path=self.urls['list_p'], follow=True)
|
||||||
|
get_resp_a = self.c1.get(path=self.urls['list_a'], follow=True)
|
||||||
|
get_resp_d = self.c1.get(path=self.urls['list_d'], follow=True)
|
||||||
|
self.assertEqual(get_resp_p.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(get_resp_a.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(get_resp_d.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_get_no_code_niv_org(self):
|
||||||
|
"""
|
||||||
|
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas d'attribut code_niv_org
|
||||||
|
"""
|
||||||
|
get_resp_p = self.c2.get(path=self.urls['list_p'], follow=True)
|
||||||
|
get_resp_a = self.c2.get(path=self.urls['list_a'], follow=True)
|
||||||
|
get_resp_d = self.c2.get(path=self.urls['list_d'], follow=True)
|
||||||
|
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_get_no_reading_rights(self):
|
||||||
|
"""
|
||||||
|
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas les droits de lecture
|
||||||
|
"""
|
||||||
|
get_resp_p = self.c3.get(path=self.urls['list_p'], follow=True)
|
||||||
|
get_resp_a = self.c3.get(path=self.urls['list_a'], follow=True)
|
||||||
|
get_resp_d = self.c3.get(path=self.urls['list_d'], follow=True)
|
||||||
|
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_get_not_pcp_not_fil(self):
|
||||||
|
"""
|
||||||
|
Teste la requete GET sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire est ni pcp ni filiere
|
||||||
|
"""
|
||||||
|
get_resp_p = self.c4.get(path=self.urls['list_p'], follow=True)
|
||||||
|
get_resp_a = self.c4.get(path=self.urls['list_a'], follow=True)
|
||||||
|
get_resp_d = self.c4.get(path=self.urls['list_d'], follow=True)
|
||||||
|
self.assertEqual(get_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(get_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_put_ok_fe_fil(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PUT sur des postes et des administrés sachant que :
|
||||||
|
- Le gestionnaire est authentifié,
|
||||||
|
- Qu'il a les droits de lecture et d'écriture,
|
||||||
|
- Qu'il est PCP et FIL,
|
||||||
|
- Qu'il est associé à deux code_niv_org4,
|
||||||
|
- Les instances qu'il édite sont dans son FE et dans sa filiere
|
||||||
|
"""
|
||||||
|
# Pas de test put pour les décisions car la modification de plusieurs décisions n'est pas possible dans Ogure
|
||||||
|
put_resp_p = self.c1.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||||
|
put_resp_a = self.c1.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||||
|
self.assertEqual(put_resp_p.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(put_resp_a.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_put_ok_fe(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PUT sur des postes et des administrés sachant que :
|
||||||
|
- Le gestionnaire est authentifié,
|
||||||
|
- Qu'il a les droits de lecture et d'écriture,
|
||||||
|
- Qu'il est PCP et FIL,
|
||||||
|
- Qu'il est associé à un code_niv_org4,
|
||||||
|
- Les instances qu'il édite sont dans son FE MAIS PAS dans sa FIL
|
||||||
|
"""
|
||||||
|
# Pas de test put pour les décisions car la modification de plusieurs décisions n'est pas possible dans Ogure
|
||||||
|
put_resp_p = self.c5.put(path=self.urls['list_p'], data=self.put_datas['data_p3'], follow=True)
|
||||||
|
put_resp_a = self.c5.put(path=self.urls['list_a'], data=self.put_datas['data_a3'], follow=True)
|
||||||
|
self.assertEqual(put_resp_p.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(put_resp_a.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_put_no_writing_rights(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PUT sur des postes et des administrés sachant qu'il n'a pas les droits d'écriture'
|
||||||
|
"""
|
||||||
|
put_resp_p = self.c6.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||||
|
put_resp_a = self.c6.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||||
|
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_put_no_fe_no_fil(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PUT sur des postes et des administrés sachant qu'il n'a pas de FE ni de FIL associé'
|
||||||
|
"""
|
||||||
|
put_resp_p = self.c7.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||||
|
put_resp_a = self.c7.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||||
|
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_put_no_instances_ok(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PUT sur des postes et des administrés sachant qu'il n'a pas d'instances dans son FE ou dans sa FIL
|
||||||
|
"""
|
||||||
|
put_resp_p = self.c8.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||||
|
put_resp_a = self.c8.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||||
|
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_put_not_ok(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PUT sur des postes et des administrés sachant que les instances modifiées ne sont ni dans le FE ni dans la FIL du gestionnaire
|
||||||
|
"""
|
||||||
|
put_resp_p = self.c9.put(path=self.urls['list_p'], data=self.put_datas['data_p1'], follow=True)
|
||||||
|
put_resp_a = self.c9.put(path=self.urls['list_a'], data=self.put_datas['data_a1'], follow=True)
|
||||||
|
self.assertEqual(put_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(put_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_no_code_niv_org(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas d'attribut code_niv_org
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c2.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c2.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c2.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_no_reading_rights(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire n'a pas les droits de lecture
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c3.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c3.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c3.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_not_pcp_not_fil_not_itd(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur les vues PosteView, AdministreView et DecisionView sachant que le gestionnaire est ni pcp ni filiere
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c4.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c4.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c4.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_ok_fe_fil(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur un poste, un administré et une décision sachant que :
|
||||||
|
- Le gestionnaire est authentifié,
|
||||||
|
- Qu'il a les droits de lecture et d'écriture,
|
||||||
|
- Qu'il est PCP et FIL,
|
||||||
|
- Qu'il est associé à deux code_niv_org4,
|
||||||
|
- Les instances qu'il édite sont dans son FE et dans sa filiere
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c1.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c1.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c1.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_patch_ok_fe(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur un poste, un administré et une décision sachant que :
|
||||||
|
- Le gestionnaire est authentifié,
|
||||||
|
- Qu'il a les droits de lecture et d'écriture,
|
||||||
|
- Qu'il est PCP et FIL,
|
||||||
|
- Qu'il est associé à un code_niv_org4,
|
||||||
|
- Les instances qu'il édite sont dans son FE MAIS PAS dans sa FIL
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c5.patch(path=self.urls['instance_p3'], data=self.patch_datas['data_p3'], follow=True)
|
||||||
|
patch_resp_a = self.c5.patch(path=self.urls['instance_a3'], data=self.patch_datas['data_a3'], follow=True)
|
||||||
|
patch_resp_d = self.c5.patch(path=self.urls['instance_d3'], data=self.patch_datas['data_d3'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_patch_ok_itd(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur un poste sachant que :
|
||||||
|
- Le gestionnaire est authentifié,
|
||||||
|
- Qu'il a les droits de lecture et d'écriture,
|
||||||
|
- Qu'il est ITD,
|
||||||
|
- Qu'il est associé à un code_niv_org4,
|
||||||
|
- Le poste édité est marqué ITD
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c10.patch(path=self.urls['instance_p5'], data=self.patch_datas['data_p5'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_patch_no_writing_rights(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur des postes et des administrés sachant qu'il n'a pas les droits d'écriture'
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c6.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c6.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c6.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_no_niv_org(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur des postes et des administrés sachant qu'il n'a pas d'attribut niv_org'
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c7.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c7.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c7.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_no_instances_ok(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur des postes et des administrés sachant qu'il n'a pas d'instances dans son FE ou dans sa FIL
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c8.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c8.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c8.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_not_ok_fe_fil(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur un poste, un administré et une décision sachant que les instances modifiées ne sont ni dans le FE ni dans la FIL du gestionnaire
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c9.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
patch_resp_a = self.c9.patch(path=self.urls['instance_a1'], data=self.patch_datas['data_a1'], follow=True)
|
||||||
|
patch_resp_d = self.c9.patch(path=self.urls['instance_d1'], data=self.patch_datas['data_d1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(patch_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_patch_not_ok_itd(self):
|
||||||
|
"""
|
||||||
|
Teste la requete PATCH sur un poste sachant que l'instance modifiée n'est pas marquée ITD
|
||||||
|
"""
|
||||||
|
patch_resp_p = self.c10.patch(path=self.urls['instance_p1'], data=self.patch_datas['data_p1'], follow=True)
|
||||||
|
self.assertEqual(patch_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_delete_ok(self):
|
||||||
|
delete_resp_p = self.c1.delete(path=self.urls['instance_p1'], follow=True)
|
||||||
|
delete_resp_a = self.c1.delete(path=self.urls['instance_a1'], follow=True)
|
||||||
|
self.assertEqual(delete_resp_p.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
self.assertEqual(delete_resp_a.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def test_delete_decision_ok(self):
|
||||||
|
# On teste la suppression de décision séparément des autres suppressions sans quoi instance_d ferait référence à un administré supprimé
|
||||||
|
delete_resp_d = self.c1.delete(path=self.urls['instance_d1'], follow=True)
|
||||||
|
self.assertEqual(delete_resp_d.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
def test_delete_not_ok(self):
|
||||||
|
# delete_resp_p = self.c9.delete(path=self.urls['instance_p1'], follow=True)
|
||||||
|
# delete_resp_a = self.c9.delete(path=self.urls['instance_a1'], follow=True)
|
||||||
|
delete_resp_d = self.c9.delete(path=self.urls['instance_d1'], follow=True)
|
||||||
|
# self.assertEqual(delete_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
# self.assertEqual(delete_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(delete_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
def test_head_ok(self):
|
||||||
|
head_resp_p = self.c1.head(path=self.urls['list_p'], follow=True)
|
||||||
|
head_resp_a = self.c1.head(path=self.urls['list_a'], follow=True)
|
||||||
|
head_resp_d = self.c1.head(path=self.urls['list_d'], follow=True)
|
||||||
|
self.assertEqual(head_resp_p.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(head_resp_a.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(head_resp_d.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_head_no_reading_rights(self):
|
||||||
|
head_resp_p = self.c3.head(path=self.urls['list_p'], follow=True)
|
||||||
|
head_resp_a = self.c3.head(path=self.urls['list_a'], follow=True)
|
||||||
|
head_resp_d = self.c3.head(path=self.urls['list_d'], follow=True)
|
||||||
|
self.assertEqual(head_resp_p.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(head_resp_a.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(head_resp_d.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
140
backend-django/backend/tests/initial.py
Normal file
140
backend-django/backend/tests/initial.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
"""
|
||||||
|
Ce fichier n'est utilisé que pour les tests.
|
||||||
|
"""
|
||||||
|
# from ..utils_scoring import notations_test
|
||||||
|
# from django.test import TestCase
|
||||||
|
# from numpy import fliplr
|
||||||
|
# from numpy.core.fromnumeric import shape
|
||||||
|
# import pandas as pd
|
||||||
|
# from .utils_scoring import encoding,notePonderee,notations_test
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
|
|
||||||
|
# class InsertModelTest(TestCase):
|
||||||
|
# # Fonction pour la table Garnisons
|
||||||
|
# def test_scoring(self):
|
||||||
|
# """Pour tester la fonction de scoring
|
||||||
|
# """
|
||||||
|
# mut_test = notations_test()
|
||||||
|
# data_test = mut_test.to_dict('records')
|
||||||
|
# data_source = [{
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 1,
|
||||||
|
# 'administres_id_sap': 9984,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.5,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''},
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 1,
|
||||||
|
# 'administres_id_sap': 56754,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.0,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''},
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 1,
|
||||||
|
# 'administres_id_sap': 78905,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.0,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 2,
|
||||||
|
# 'administres_id_sap': 9984,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.0,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 2,
|
||||||
|
# 'administres_id_sap': 56754,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.5,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 2,
|
||||||
|
# 'administres_id_sap': 78905,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.0,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 3,
|
||||||
|
# 'administres_id_sap': 9984,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.0,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 3,
|
||||||
|
# 'administres_id_sap': 56754,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.0,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# 'mu_id': 0,
|
||||||
|
# 'poste_id': 3,
|
||||||
|
# 'administres_id_sap': 78905,
|
||||||
|
# 'pam_id': 1,
|
||||||
|
# 'mu_date_execution': '14/06/2021',
|
||||||
|
# 'mu_note_militaire': 0.5,
|
||||||
|
# 'mu_flag_cple_ideal': 0,
|
||||||
|
# 'mu_decision': '',
|
||||||
|
# 'mu_date_decision': '',
|
||||||
|
# 'mu_notes_gestionnaire': '',
|
||||||
|
# 'mu_notes_partagees': ''
|
||||||
|
# }]
|
||||||
|
# if data_source == data_test :
|
||||||
|
# test_value = True
|
||||||
|
# else :
|
||||||
|
# test_value = False
|
||||||
|
# self.assertIs(test_value,True)
|
||||||
|
|
||||||
3
backend-django/backend/tests/models/__init__.py
Normal file
3
backend-django/backend/tests/models/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .administre import *
|
||||||
|
from .calcul import *
|
||||||
|
from .poste import *
|
||||||
39
backend-django/backend/tests/models/administre.py
Normal file
39
backend-django/backend/tests/models/administre.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from ...models import Administre, StatutPamChoices
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class AdministreTestCase(TestCase):
|
||||||
|
|
||||||
|
def test_attributes_statut_pam(self):
|
||||||
|
""" vérifie que le comportement reste correct avec l'ajout de nouveaux attributs """
|
||||||
|
|
||||||
|
attr_calc = 'calc_enabled'
|
||||||
|
attr_dec = 'dec_enabled'
|
||||||
|
for choice in StatutPamChoices:
|
||||||
|
self.assertIsInstance(choice.label, str, f"{choice} : le libellé n'a pas le bon type")
|
||||||
|
self.assertIsInstance(choice.value, str, f"{choice} : la valeur n'a pas le bon type")
|
||||||
|
self.assertEqual(choice.value, choice, f"{choice} : n'est pas égal à sa valeur")
|
||||||
|
self.assertIsInstance(getattr(choice, attr_calc), bool, f"{choice} : l'attribut {attr_calc} n'a pas le bon type")
|
||||||
|
self.assertIsInstance(getattr(choice, attr_dec), bool, f"{choice} : l'attribut {attr_dec} n'a pas le bon type")
|
||||||
|
|
||||||
|
def test_constraint_statut_pam(self):
|
||||||
|
""" vérifie qu'il n'est pas possible de stocker n'importe quoi en base """
|
||||||
|
|
||||||
|
valides = StatutPamChoices.values
|
||||||
|
invalide = 'autre'
|
||||||
|
|
||||||
|
def given():
|
||||||
|
self.assertTrue(valides, 'il devrait exister des choix valides')
|
||||||
|
self.assertNotIn(invalide, valides, 'le test nécessite une valeur invalide')
|
||||||
|
given()
|
||||||
|
|
||||||
|
id_sap = 1
|
||||||
|
|
||||||
|
# valide : création OK
|
||||||
|
for valide in valides:
|
||||||
|
Administre.objects.create(pk=id_sap, a_statut_pam=valide)
|
||||||
|
id_sap = id_sap + 1
|
||||||
|
|
||||||
|
# invalide : création KO
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
Administre.objects.create(pk=id_sap, a_statut_pam=invalide)
|
||||||
34
backend-django/backend/tests/models/calcul.py
Normal file
34
backend-django/backend/tests/models/calcul.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from django.db.utils import IntegrityError
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from ...models.calcul import Calcul, SousVivier
|
||||||
|
from ...models.calcul import StatutCalculChoices as StatutCalcul
|
||||||
|
|
||||||
|
|
||||||
|
class CalculTestCase(TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
for i in range(1, 15):
|
||||||
|
SousVivier.objects.create(pk=str(i))
|
||||||
|
|
||||||
|
def test_constraint_statut(self):
|
||||||
|
valides = StatutCalcul.values
|
||||||
|
invalide = 'autre'
|
||||||
|
|
||||||
|
def given():
|
||||||
|
self.assertTrue(valides, 'il devrait exister des choix valides')
|
||||||
|
self.assertNotIn(invalide, valides, 'le test nécessite une valeur invalide')
|
||||||
|
given()
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
|
||||||
|
# valide : création OK
|
||||||
|
for valide in valides:
|
||||||
|
Calcul.objects.create(pk=str(i), ca_date_debut=timezone.now(), ca_statut=valide, ca_statut_pourcentage=0)
|
||||||
|
i = i + 1
|
||||||
|
|
||||||
|
# invalide : création KO
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
Calcul.objects.create(pk=str(i), ca_date_debut=timezone.now(), ca_statut=invalide, ca_statut_pourcentage=0)
|
||||||
34
backend-django/backend/tests/models/poste.py
Normal file
34
backend-django/backend/tests/models/poste.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from ...models import AvisPosteChoices as AvisPoste, Poste, PropositionsArmementChoices as PropositionsArmement
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
class PosteTestCase(TestCase):
|
||||||
|
|
||||||
|
def test_attributes_avis(self):
|
||||||
|
""" vérifie que le comportement reste correct avec l'ajout de nouveaux attributs """
|
||||||
|
|
||||||
|
attr_calc = 'calc_enabled'
|
||||||
|
attr_dec = 'dec_enabled'
|
||||||
|
for choice in AvisPoste:
|
||||||
|
self.assertIsInstance(choice.label, str, f"{choice} : le libellé n'a pas le bon type")
|
||||||
|
self.assertIsInstance(choice.value, str, f"{choice} : la valeur n'a pas le bon type")
|
||||||
|
self.assertEqual(choice.value, choice, f"{choice} : n'est pas égal à sa valeur")
|
||||||
|
self.assertIsInstance(getattr(choice, attr_calc), bool, f"{choice} : l'attribut {attr_calc} n'a pas le bon type")
|
||||||
|
self.assertIsInstance(getattr(choice, attr_dec), bool, f"{choice} : l'attribut {attr_dec} n'a pas le bon type")
|
||||||
|
|
||||||
|
def test_constraint_propositions_armement(self):
|
||||||
|
valides = PropositionsArmement.values
|
||||||
|
invalide = 'autre'
|
||||||
|
|
||||||
|
def given():
|
||||||
|
self.assertTrue(valides, 'il devrait exister des choix valides')
|
||||||
|
self.assertNotIn(invalide, valides, 'le test nécessite une valeur invalide')
|
||||||
|
given()
|
||||||
|
|
||||||
|
# valide : création OK
|
||||||
|
for valide in valides:
|
||||||
|
Poste.objects.create(pk=valide, propositions_armement=valide)
|
||||||
|
|
||||||
|
# invalide : création KO
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
Poste.objects.create(pk=invalide, propositions_armement=invalide)
|
||||||
75
backend-django/backend/tests/pam/__init__.py
Normal file
75
backend-django/backend/tests/pam/__init__.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
import pandas as pd
|
||||||
|
from rest_framework.test import APIClient, APITestCase
|
||||||
|
from ...utils_extraction import to_table_pam
|
||||||
|
|
||||||
|
from ...models import (Administre, CustomUser, Decision, DecisionChoices,
|
||||||
|
Domaine, Filiere, FormationEmploi, Garnison, Poste,
|
||||||
|
RefGest, RefOrg, RefSvFil, SousVivier, Administres_Pams, Postes_Pams, PAM)
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PamTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super().setUpClass()
|
||||||
|
cls.date = datetime.date(2023,1,1)
|
||||||
|
cls.pam_2022 = PAM.objects.create(pam_id='2022',pam_date=datetime.date(2022,7,15), pam_libelle= "PAM de l'année 2022", pam_statut='PAM en cours')
|
||||||
|
cls.pam_2023 = PAM.objects.create(pam_id='2023',pam_date=datetime.date(2022,7,15), pam_libelle= "PAM de l'année 2023", pam_statut='PAM A+1')
|
||||||
|
|
||||||
|
|
||||||
|
def test_extraction_pam(self):
|
||||||
|
"""
|
||||||
|
Construit la table PAM qui servira à récuperer l'année du PAM et piloter l'affichage des données en fonction du bon pam pour chaque tables
|
||||||
|
Cloture le PAM en le sauvegardant dans un fichier excel
|
||||||
|
"""
|
||||||
|
annee_pam = str(self.date.year)
|
||||||
|
annee_pam_suivant = str(self.date.year + 1)
|
||||||
|
|
||||||
|
pam=pd.DataFrame(columns = ['pam_id','pam_date', 'pam_libelle','pam_statut'])
|
||||||
|
pam_id = annee_pam
|
||||||
|
pam_id_suivant = annee_pam_suivant
|
||||||
|
pam_libelle = f"PAM de l'année {annee_pam}"
|
||||||
|
pam_libelle_suivant = f"PAM de l'année {annee_pam_suivant}"
|
||||||
|
pam_date = self.date
|
||||||
|
pam_date_suivant = pam_date
|
||||||
|
pam_statut = "PAM en cours"
|
||||||
|
pam_statut_suivant = "PAM A+1"
|
||||||
|
|
||||||
|
sorg_id = "SORG"
|
||||||
|
sorg_libelle = "SORG"
|
||||||
|
sorg_statut = "SORG"
|
||||||
|
|
||||||
|
pam['pam_id'] = [pam_id,pam_id_suivant, sorg_id]
|
||||||
|
pam['pam_date'] = [pam_date,pam_date_suivant, pam_date]
|
||||||
|
pam['pam_libelle'] = [pam_libelle,pam_libelle_suivant,sorg_libelle]
|
||||||
|
pam['pam_statut'] = [pam_statut,pam_statut_suivant,sorg_statut]
|
||||||
|
|
||||||
|
return pam
|
||||||
|
|
||||||
|
|
||||||
|
def test_insertion_pam(self):
|
||||||
|
self.liste_create = []
|
||||||
|
self.liste_update = []
|
||||||
|
self.update_header = ['pam_date','pam_libelle','pam_statut']
|
||||||
|
self.df = self.test_extraction_pam()
|
||||||
|
for i in range(self.df.shape[0]):
|
||||||
|
self.pams = PAM.objects.filter(pam_id=self.df.at[i,'pam_id'])
|
||||||
|
self.pam = PAM(pam_id=self.df.at[i, 'pam_id'],pam_date=self.df.at[i, 'pam_date'], pam_libelle=self.df.at[i, 'pam_libelle'],
|
||||||
|
pam_statut=self.df.at[i, 'pam_statut'])
|
||||||
|
|
||||||
|
if self.pam.pam_id in self.pams.values_list('pam_id', flat = True):
|
||||||
|
self.liste_update.append(self.pam)
|
||||||
|
else:
|
||||||
|
cloture=PAM.objects.filter(pam_statut="PAM en cours")
|
||||||
|
cloture.update(pam_statut ="PAM clôturé")
|
||||||
|
self.liste_create.append(self.pam)
|
||||||
|
|
||||||
|
if self.liste_create:
|
||||||
|
PAM.objects.bulk_create(self.liste_create)
|
||||||
|
if self.liste_update:
|
||||||
|
PAM.objects.bulk_update(self.liste_update, fields=self.update_header)
|
||||||
6
backend-django/backend/tests/utils/__init__.py
Normal file
6
backend-django/backend/tests/utils/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from .alimentation import *
|
||||||
|
from .decisions import *
|
||||||
|
from .decorators import *
|
||||||
|
from .functions import *
|
||||||
|
from .logging import *
|
||||||
|
from .predicates import *
|
||||||
56
backend-django/backend/tests/utils/alimentation.py
Normal file
56
backend-django/backend/tests/utils/alimentation.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
from ...utils.alimentation import BOCols as Cols
|
||||||
|
|
||||||
|
|
||||||
|
class BOColsTestCase(SimpleTestCase):
|
||||||
|
""" tests pour BOCols """
|
||||||
|
|
||||||
|
def test_attributes(self):
|
||||||
|
""" vérifie que les nouveaux attributs sont renseignés correctement """
|
||||||
|
|
||||||
|
for col in Cols:
|
||||||
|
self.assertIsInstance(col.value, str, f"{col} : la valeur n'a pas le bon type")
|
||||||
|
|
||||||
|
def test_columns(self):
|
||||||
|
""" vérifie les colonnes renvoyées """
|
||||||
|
members = list(Cols)
|
||||||
|
|
||||||
|
col_names = [col.value for col in members]
|
||||||
|
self.assertCountEqual(col_names, Cols.columns(), "les noms de colonnes (sans argument) ne sont pas les bons")
|
||||||
|
|
||||||
|
col = members[0]
|
||||||
|
col_names = [col.value]
|
||||||
|
self.assertCountEqual(col_names, Cols.columns(col), "les noms de colonnes (un argument) ne sont pas les bons")
|
||||||
|
|
||||||
|
col_list = members[0:2]
|
||||||
|
col_names = [col.value for col in col_list]
|
||||||
|
self.assertCountEqual(col_names, Cols.columns(*col_list), "les noms de colonnes (plusieurs arguments) ne sont pas les bons")
|
||||||
|
|
||||||
|
def test_col_mapping(self):
|
||||||
|
""" vérifie les correspondances renvoyés """
|
||||||
|
|
||||||
|
members = list(Cols)
|
||||||
|
std_mapping = {'a': 1, 'b': 'test'}
|
||||||
|
enum_mapping = {members[0]: (2, 3), members[1]: members[1].name}
|
||||||
|
mapping_before = {**enum_mapping, **std_mapping}
|
||||||
|
mapping_after = Cols.col_mapping(mapping_before)
|
||||||
|
self.assertTrue(all(k in mapping_after for k in std_mapping.keys()), "toutes les clés standard doivent faire partie du mapping final")
|
||||||
|
self.assertTrue(all(v == mapping_after.get(k) for k, v in std_mapping.items()), "les valeurs de clés standard ne doivent pas changer")
|
||||||
|
self.assertTrue(all(k.value in mapping_after for k in enum_mapping.keys()), f"tous les noms de colonnes de clés de type {Cols.__name__} doivent faire partie du mapping final")
|
||||||
|
self.assertTrue(all(v == mapping_after.get(k.value) for k, v in enum_mapping.items()), f"les valeurs de clés de type {Cols.__name__} ne doivent pas changer")
|
||||||
|
|
||||||
|
def test_converters(self):
|
||||||
|
""" vérifie les convertisseurs renvoyés """
|
||||||
|
members = list(Cols)
|
||||||
|
|
||||||
|
col_names = [col.value for col in members if col.converter]
|
||||||
|
self.assertCountEqual(col_names, Cols.converters().keys(), "les convertisseurs (sans argument) ne sont pas les bons")
|
||||||
|
|
||||||
|
col = members[0]
|
||||||
|
col_names = [col.value]
|
||||||
|
self.assertCountEqual(col_names, Cols.converters(col).keys(), "les convertisseurs (un argument) ne sont pas les bons")
|
||||||
|
|
||||||
|
col_list = members[0:2]
|
||||||
|
col_names = [col.value for col in col_list]
|
||||||
|
self.assertCountEqual(col_names, Cols.converters(*col_list).keys(), "les convertisseurs (plusieurs arguments) ne sont pas les bons")
|
||||||
283
backend-django/backend/tests/utils/decisions.py
Normal file
283
backend-django/backend/tests/utils/decisions.py
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
from typing import Callable, List, Optional, Tuple, Union
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from ...models import Administre
|
||||||
|
from ...models import AvisPosteChoices as AvisPoste
|
||||||
|
from ...models import CustomUser, Decision, DecisionChoices, Poste, SousVivier
|
||||||
|
from ...models import StatutPamChoices as StatutPam
|
||||||
|
from ...utils.decisions import (DECISIONS, KEY_CHOICES, KEY_CREATE,
|
||||||
|
KEY_PROFILES, KEY_UPDATE, ExtraDecisions,
|
||||||
|
get_all_decisions, get_available_decisions)
|
||||||
|
from ...utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||||
|
get_profiles_by_adm)
|
||||||
|
|
||||||
|
|
||||||
|
class DecisionsTest(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
""" vérifie qu'il n'existe aucun administré ou poste avant le test """
|
||||||
|
|
||||||
|
self.assertEqual(Administre.objects.exists(), False, "pas d'administré avant un test")
|
||||||
|
self.assertEqual(Poste.objects.exists(), False, "pas de poste avant un test")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
""" supprime tous les administrés, les postes, les décisions en fin de test """
|
||||||
|
|
||||||
|
Decision.objects.all().delete()
|
||||||
|
Poste.objects.all().delete()
|
||||||
|
Administre.objects.all().delete()
|
||||||
|
|
||||||
|
def __load_administres(self) -> List[Administre]:
|
||||||
|
""" charge les administrés de la base à partir des IDs """
|
||||||
|
|
||||||
|
self.assertTrue(self.adm_ids, "il est nécessaire de renseigner des IDs d'administrés pour le chargement")
|
||||||
|
return list(Administre.objects.filter(pk__in=self.adm_ids or ()))
|
||||||
|
|
||||||
|
def __setup_decisions(self, decision: Optional[Union[DecisionChoices, ExtraDecisions]]) -> None:
|
||||||
|
""" supprime et recrée (si nécessaire) les postes et les décisions des administrés """
|
||||||
|
|
||||||
|
Decision.objects.all().delete()
|
||||||
|
Poste.objects.all().delete()
|
||||||
|
if decision and decision != ExtraDecisions.EMPTY:
|
||||||
|
for adm_id in self.adm_ids:
|
||||||
|
poste = Poste.objects.create(pk=str(adm_id), p_avis=AvisPoste.P1)
|
||||||
|
Decision.objects.create(administre_id=adm_id, poste=poste, de_decision=decision)
|
||||||
|
|
||||||
|
def __do_for_profiles(self, action: Callable[[Profiles], None], profiles: Tuple[Profiles], decisions: Tuple[Union[DecisionChoices, ExtraDecisions]] = ()) -> None:
|
||||||
|
"""
|
||||||
|
exécute une action (un test) avec un sous-test :
|
||||||
|
- soit pour chaque profil (utilise un mock)
|
||||||
|
- soit pour chaque profil et chaque statut de décision (utilise un mock)
|
||||||
|
implicite : self.adm_ids
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run_subTest(profile: Profiles, decision: Optional[Union[DecisionChoices, ExtraDecisions]] = None) -> None:
|
||||||
|
with self.subTest(profile=profile.name, **({'decision': decision.name} if decision else {})):
|
||||||
|
@mock.patch(
|
||||||
|
f'{get_available_decisions.__module__}.{get_profiles_by_adm.__name__}',
|
||||||
|
return_value={adm_id: {KEY_READ: (), KEY_WRITE: (profile,)} for adm_id in self.adm_ids}
|
||||||
|
)
|
||||||
|
def do_with_profile(mock):
|
||||||
|
action(profile, decision)
|
||||||
|
do_with_profile()
|
||||||
|
|
||||||
|
for p in profiles:
|
||||||
|
if decisions:
|
||||||
|
for decision in decisions:
|
||||||
|
self.__setup_decisions(decision)
|
||||||
|
run_subTest(p, decision)
|
||||||
|
else:
|
||||||
|
run_subTest(p)
|
||||||
|
|
||||||
|
def __assert_common(self, decisions_by_adm):
|
||||||
|
"""
|
||||||
|
assertions communes à propos des décisions renvoyées pour les administrés
|
||||||
|
implicite : self.adm_ids
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.assertIsInstance(decisions_by_adm, dict, "le résultat n'est jamais None")
|
||||||
|
for adm_id in self.adm_ids:
|
||||||
|
self.assertTrue(adm_id in decisions_by_adm, "tous les administrés doivent être présents dans le résultat")
|
||||||
|
|
||||||
|
decisions = decisions_by_adm.get(adm_id)
|
||||||
|
self.assertIsInstance(decisions, dict, "le format des décisions est un dictionnaire")
|
||||||
|
for key in (KEY_CREATE, KEY_UPDATE):
|
||||||
|
self.assertTrue(key in decisions, f"les décisions doivent contenir la clé {key}")
|
||||||
|
self.assertIsInstance(decisions.get(key), tuple, f"la valeur de {key} des décisions n'a pas le type attendu")
|
||||||
|
|
||||||
|
def test_tree_attribute(self):
|
||||||
|
""" vérifie que le comportement reste correct avec l'ajout de nouveaux attributs """
|
||||||
|
|
||||||
|
attr_trees = 'trees'
|
||||||
|
for choice in DecisionChoices:
|
||||||
|
self.assertIsInstance(choice.label, str, f"{choice} : le libellé n'a pas le bon type")
|
||||||
|
self.assertIsInstance(choice.value, str, f"{choice} : la valeur n'a pas le bon type")
|
||||||
|
self.assertEqual(choice.value, choice, f"{choice} : n'est pas égal à sa valeur")
|
||||||
|
self.assertIsInstance(getattr(choice, attr_trees), tuple, f"{choice} : l'attribut {attr_trees} n'a pas le bon type")
|
||||||
|
|
||||||
|
def test_champ_decisions(self):
|
||||||
|
""" vérifie la structure de DECISIONS """
|
||||||
|
|
||||||
|
to_test = DECISIONS
|
||||||
|
|
||||||
|
self.assertIsInstance(to_test, dict, "la structure n'a pas le type attendu")
|
||||||
|
self.assertTrue(to_test, "la structure ne doit pas être vide")
|
||||||
|
|
||||||
|
for key, value in to_test.items():
|
||||||
|
self.assertTrue(key is None or isinstance(key, DecisionChoices), f"la décision {key} n'a pas le type attendu")
|
||||||
|
self.assertIsInstance(value, dict, f"la valeur pour la décision {key} n'a pas le type attendu")
|
||||||
|
|
||||||
|
profiles = value.get(KEY_PROFILES)
|
||||||
|
self.assertTrue(profiles is None or isinstance(profiles, tuple), f"la valeur de {KEY_PROFILES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||||
|
for p in profiles:
|
||||||
|
self.assertIsInstance(p, Profiles, f"un élément de la clé {KEY_PROFILES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||||
|
|
||||||
|
choices = value.get(KEY_CHOICES)
|
||||||
|
self.assertTrue(choices is None or isinstance(choices, tuple), f"la valeur de {KEY_CHOICES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||||
|
for c in choices:
|
||||||
|
self.assertIsInstance(c, (DecisionChoices, ExtraDecisions), f"un élément de la clé {KEY_CHOICES} de la valeur pour la décision {key} n'a pas le type attendu")
|
||||||
|
|
||||||
|
def test_get_all_decisions(self):
|
||||||
|
""" vérifie les valeurs de get_all_decisions """
|
||||||
|
|
||||||
|
to_test = get_all_decisions()
|
||||||
|
|
||||||
|
self.assertIsInstance(to_test, tuple, "le résultat n'a pas le type attendu")
|
||||||
|
self.assertTrue(to_test, "le résultat ne doit pas être vide")
|
||||||
|
|
||||||
|
for value in to_test:
|
||||||
|
self.assertIsInstance(value, DecisionChoices, f"la valeur {value} n'a pas le type attendu")
|
||||||
|
|
||||||
|
def test_pam_wihout_choice(self):
|
||||||
|
""" pour ces statuts PAM il n'y a aucun choix """
|
||||||
|
|
||||||
|
user = CustomUser()
|
||||||
|
self.adm_ids = tuple(a.a_id_sap for a in (
|
||||||
|
Administre.objects.create(pk=i + 1, a_statut_pam=status) for i, status in enumerate(StatutPam) if not status.dec_enabled
|
||||||
|
))
|
||||||
|
|
||||||
|
def action(_p, _d):
|
||||||
|
decisions_by_adm = get_available_decisions(self.__load_administres(), user=user)
|
||||||
|
|
||||||
|
self.__assert_common(decisions_by_adm)
|
||||||
|
for adm_id in self.adm_ids:
|
||||||
|
decisions = decisions_by_adm.get(adm_id)
|
||||||
|
self.assertFalse(decisions.get(KEY_CREATE))
|
||||||
|
self.assertFalse(decisions.get(KEY_UPDATE))
|
||||||
|
|
||||||
|
self.__do_for_profiles(action, profiles=tuple(Profiles))
|
||||||
|
|
||||||
|
def test_pam_with_choices(self):
|
||||||
|
""" pour ces statuts PAM c'est l'arbre de décision qui est utilisé """
|
||||||
|
|
||||||
|
user = CustomUser()
|
||||||
|
self.adm_ids = tuple(a.a_id_sap for a in (
|
||||||
|
Administre.objects.create(pk=i + 1, a_statut_pam=status) for i, status in enumerate(StatutPam) if status.dec_enabled
|
||||||
|
))
|
||||||
|
D = DecisionChoices
|
||||||
|
EX = ExtraDecisions
|
||||||
|
|
||||||
|
def action(p, d):
|
||||||
|
decisions_by_adm = get_available_decisions(self.__load_administres(), user=user)
|
||||||
|
|
||||||
|
self.__assert_common(decisions_by_adm)
|
||||||
|
for adm_id in self.adm_ids:
|
||||||
|
decisions = decisions_by_adm.get(adm_id)
|
||||||
|
|
||||||
|
# create
|
||||||
|
decisions_create = decisions.get(KEY_CREATE)
|
||||||
|
if p == Profiles.FILIERE:
|
||||||
|
self.assertCountEqual(decisions_create, (D.PROPOSITION_FE, D.HME_PROPOSITION_VIVIER))
|
||||||
|
else:
|
||||||
|
self.assertFalse(decisions_create)
|
||||||
|
|
||||||
|
# update
|
||||||
|
decisions_update = decisions.get(KEY_UPDATE)
|
||||||
|
if not d and p == Profiles.FILIERE:
|
||||||
|
self.assertCountEqual(decisions_update, (D.PROPOSITION_FE, D.HME_PROPOSITION_VIVIER))
|
||||||
|
|
||||||
|
elif d == D.PROPOSITION_FE and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||||
|
self.assertCountEqual(decisions_update, (D.DIALOGUE_EN_COURS, D.FOREMP_EN_COURS))
|
||||||
|
|
||||||
|
elif d == D.DIALOGUE_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||||
|
self.assertCountEqual(decisions_update, (D.DIALOGUE_TERMINE, D.DIALOGUE_INFRUCTUEUX))
|
||||||
|
|
||||||
|
elif d == D.DIALOGUE_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||||
|
self.assertCountEqual(decisions_update, (D.FOREMP_EN_COURS,))
|
||||||
|
|
||||||
|
elif d == D.DIALOGUE_INFRUCTUEUX and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||||
|
self.assertCountEqual(decisions_update, (D.FOREMP_EN_COURS, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.FOREMP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||||
|
self.assertCountEqual(decisions_update, (D.FOREMP_TERMINE,))
|
||||||
|
|
||||||
|
elif d == D.FOREMP_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL):
|
||||||
|
self.assertCountEqual(decisions_update, (D.PREPOSITIONNE, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.PREPOSITIONNE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.POSITIONNE, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.POSITIONNE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.OMIP_EN_COURS, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.OMIP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.OMIP_TERMINE, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.OMIP_TERMINE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.ATTENTE_AVIONAGE, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.ATTENTE_AVIONAGE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.OMI_EN_COURS,))
|
||||||
|
|
||||||
|
elif d == D.OMI_EN_COURS and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.OMI_ACTIVE, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.OMI_ACTIVE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.OMI_ANNULE,))
|
||||||
|
|
||||||
|
elif d == D.OMI_ANNULE and p in (Profiles.PCP, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.REMIS_A_DISPOSITION,))
|
||||||
|
|
||||||
|
elif d == D.REMIS_A_DISPOSITION and p == Profiles.FILIERE:
|
||||||
|
self.assertCountEqual(decisions_update, (EX.EMPTY,))
|
||||||
|
|
||||||
|
elif d == D.HME_PROPOSITION_VIVIER and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_DIALOGUE_INITIE,))
|
||||||
|
|
||||||
|
elif d == D.HME_ETUDE_DESISTEMENT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_DESISTEMENT,))
|
||||||
|
|
||||||
|
elif d == D.HME_DESISTEMENT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.REMIS_A_DISPOSITION,))
|
||||||
|
|
||||||
|
elif d == D.HME_DIALOGUE_INITIE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_DIALOGUE_EN_COURS,))
|
||||||
|
|
||||||
|
elif d == D.HME_DIALOGUE_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_DIALOGUE_TERMINE, D.HME_DIALOGUE_INFRUCTUEUX))
|
||||||
|
|
||||||
|
elif d == D.HME_DIALOGUE_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_PREPOSITIONNE,))
|
||||||
|
|
||||||
|
elif d == D.HME_DIALOGUE_INFRUCTUEUX and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_ETUDE_DESISTEMENT, D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.HME_FOREMP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_FOREMP_TERMINE, D.HME_OMI_EN_COURS))
|
||||||
|
|
||||||
|
elif d == D.HME_FOREMP_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS))
|
||||||
|
|
||||||
|
elif d == D.HME_PREPOSITIONNE and p == Profiles.HME:
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_VALIDATION_EXPERT, D.HME_REFUS_EXPERT))
|
||||||
|
|
||||||
|
elif d == D.HME_POSITIONNE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS, D.HME_FOREMP_EN_COURS))
|
||||||
|
|
||||||
|
elif d == D.HME_VALIDATION_EXPERT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_POSITIONNE,))
|
||||||
|
|
||||||
|
elif d == D.HME_REFUS_EXPERT and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION))
|
||||||
|
|
||||||
|
elif d == D.HME_OMIP_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_OMIP_TERMINE,))
|
||||||
|
|
||||||
|
elif d == D.HME_OMIP_TERMINE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_ATTENTE_AVIONAGE, D.HME_OMI_EN_COURS))
|
||||||
|
|
||||||
|
elif d == D.HME_ATTENTE_AVIONAGE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_OMI_EN_COURS,))
|
||||||
|
|
||||||
|
elif d == D.HME_OMI_EN_COURS and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, (D.HME_OMI_ACTIVE,))
|
||||||
|
|
||||||
|
elif d == D.HME_OMI_ACTIVE and p in (Profiles.PCP, Profiles.PCP_ACTUEL, Profiles.PCP_FUTUR):
|
||||||
|
self.assertCountEqual(decisions_update, ())
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.assertFalse(decisions_update)
|
||||||
|
|
||||||
|
self.__do_for_profiles(action, profiles=tuple(Profiles), decisions=(EX.EMPTY, *get_all_decisions()))
|
||||||
85
backend-django/backend/tests/utils/decorators.py
Normal file
85
backend-django/backend/tests/utils/decorators.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
from .samples import MyClass
|
||||||
|
from ...utils.decorators import class_logger, decorate_functions, CLASS_ATTR_LOGGER
|
||||||
|
from ...utils.functions import find_class
|
||||||
|
from ...utils.logging import get_logger_name
|
||||||
|
from ...utils.predicates import func_name_is_in
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
from typing import Tuple
|
||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
|
def double_result(func):
|
||||||
|
""" décorateur pour doubler les résultats """
|
||||||
|
@functools.wraps(func)
|
||||||
|
def inner(*args, **kwargs):
|
||||||
|
return 2 * func(*args, **kwargs)
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
class DecoratorTest(SimpleTestCase):
|
||||||
|
|
||||||
|
def test_decorate_functions(self):
|
||||||
|
num = 5
|
||||||
|
a = 1
|
||||||
|
b = 2
|
||||||
|
res = a + b
|
||||||
|
res_x2 = res * 2
|
||||||
|
|
||||||
|
def given():
|
||||||
|
msg = 'le résultat initial est bien celui attendu'
|
||||||
|
self.assertEqual(MyClass.class_met(a, b), res, msg)
|
||||||
|
self.assertEqual(MyClass.static_met(a, b), res, msg)
|
||||||
|
self.assertEqual(MyClass(num).std_met(a, b), res, msg)
|
||||||
|
given()
|
||||||
|
|
||||||
|
# tout est décoré
|
||||||
|
Type = decorate_functions(double_result, lambda func: True)(MyClass)
|
||||||
|
self.assertEqual(Type.class_met(a, b), res_x2)
|
||||||
|
self.assertEqual(Type.static_met(a, b), res_x2)
|
||||||
|
self.assertEqual(Type(num).std_met(a, b), res_x2)
|
||||||
|
|
||||||
|
# une seule méthode est décorée (1ère)
|
||||||
|
Type = decorate_functions(double_result, func_name_is_in(MyClass.class_met.__name__,))(MyClass)
|
||||||
|
self.assertEqual(Type.class_met(a, b), res_x2)
|
||||||
|
self.assertEqual(Type.static_met(a, b), res)
|
||||||
|
self.assertEqual(Type(num).std_met(a, b), res)
|
||||||
|
|
||||||
|
# une seule méthode est décorée (2ème)
|
||||||
|
Type = decorate_functions(double_result, func_name_is_in(MyClass.static_met.__name__,))(MyClass)
|
||||||
|
self.assertEqual(Type.class_met(a, b), res)
|
||||||
|
self.assertEqual(Type.static_met(a, b), res_x2)
|
||||||
|
self.assertEqual(Type(num).std_met(a, b), res)
|
||||||
|
|
||||||
|
# une seule méthode est décorée (3ème)
|
||||||
|
Type = decorate_functions(double_result, func_name_is_in(MyClass.std_met.__name__,))(MyClass)
|
||||||
|
self.assertEqual(Type.class_met(a, b), res)
|
||||||
|
self.assertEqual(Type.static_met(a, b), res)
|
||||||
|
self.assertEqual(Type(num).std_met(a, b), res_x2)
|
||||||
|
|
||||||
|
# une seule méthode est décorée deux fois (3ème)
|
||||||
|
Type = decorate_functions(double_result, func_name_is_in(MyClass.std_met.__name__,))(Type)
|
||||||
|
self.assertEqual(Type.class_met(a, b), res)
|
||||||
|
self.assertEqual(Type.static_met(a, b), res)
|
||||||
|
self.assertEqual(Type(num).std_met(a, b), 2 * res_x2)
|
||||||
|
|
||||||
|
# pas de changement au niveau de la classe d'origine
|
||||||
|
self.assertEqual(MyClass.class_met(a, b), res)
|
||||||
|
self.assertEqual(MyClass.static_met(a, b), res)
|
||||||
|
self.assertEqual(MyClass(num).std_met(a, b), res)
|
||||||
|
|
||||||
|
def test_class_logger(self):
|
||||||
|
def given():
|
||||||
|
self.assertEqual(hasattr(MyClass, CLASS_ATTR_LOGGER), False, "la classe initiale n'a pas de logger")
|
||||||
|
given()
|
||||||
|
|
||||||
|
Type = class_logger(MyClass)
|
||||||
|
self.assertEqual(hasattr(Type, CLASS_ATTR_LOGGER), True)
|
||||||
|
self.assertEqual(getattr(Type, CLASS_ATTR_LOGGER).name, get_logger_name(MyClass))
|
||||||
|
|
||||||
|
# 2ème décoration
|
||||||
|
Type = class_logger(Type)
|
||||||
|
self.assertEqual(hasattr(Type, CLASS_ATTR_LOGGER), True)
|
||||||
|
self.assertEqual(getattr(Type, CLASS_ATTR_LOGGER).name, get_logger_name(MyClass))
|
||||||
|
|
||||||
|
# pas de changement au niveau de la classe d'origine
|
||||||
|
self.assertEqual(hasattr(MyClass, CLASS_ATTR_LOGGER), False)
|
||||||
30
backend-django/backend/tests/utils/functions.py
Normal file
30
backend-django/backend/tests/utils/functions.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from .samples import my_func, MyClass
|
||||||
|
from ...utils.functions import find_class
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionsTest(SimpleTestCase):
|
||||||
|
|
||||||
|
def test_find_class(self):
|
||||||
|
MainType = MyClass
|
||||||
|
main_type = MainType(11)
|
||||||
|
SubType = MyClass.MySubClass
|
||||||
|
sub_type = SubType(13)
|
||||||
|
|
||||||
|
def local_func(a: int, b: int):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
# fonction native : None
|
||||||
|
self.assertIsNone(find_class(str.join))
|
||||||
|
|
||||||
|
# fonctions : None
|
||||||
|
self.assertIsNone(find_class(local_func))
|
||||||
|
self.assertIsNone(find_class(my_func))
|
||||||
|
|
||||||
|
# méthodes
|
||||||
|
self.assertEqual(find_class(MainType.class_met), MainType)
|
||||||
|
self.assertEqual(find_class(MainType.static_met), MainType)
|
||||||
|
self.assertEqual(find_class(main_type.std_met), MainType)
|
||||||
|
self.assertEqual(find_class(SubType.sub_class_met), SubType)
|
||||||
|
self.assertEqual(find_class(SubType.sub_static_met), SubType)
|
||||||
|
self.assertEqual(find_class(sub_type.sub_std_met), SubType)
|
||||||
104
backend-django/backend/tests/utils/logging.py
Normal file
104
backend-django/backend/tests/utils/logging.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import random
|
||||||
|
import string
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
from ...utils.logging import (TAG_DATA_FEED, TAG_PERF, get_logger,
|
||||||
|
get_logger_name)
|
||||||
|
from .samples import MyClass, my_func
|
||||||
|
|
||||||
|
|
||||||
|
class LoggingTest(SimpleTestCase):
|
||||||
|
|
||||||
|
def test_get_logger_name(self):
|
||||||
|
module_name = __name__
|
||||||
|
ext_module_name = my_func.__module__
|
||||||
|
|
||||||
|
MainType = MyClass
|
||||||
|
main_type = MainType(11)
|
||||||
|
main_type_logger = f'{ext_module_name}.{MainType.__qualname__}'
|
||||||
|
SubType = MyClass.MySubClass
|
||||||
|
sub_type = SubType(13)
|
||||||
|
sub_type_logger = f'{ext_module_name}.{SubType.__qualname__}'
|
||||||
|
|
||||||
|
def local_func(a: int, b: int):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
# None
|
||||||
|
self.assertIsNone(get_logger_name())
|
||||||
|
self.assertIsNone(get_logger_name(None))
|
||||||
|
|
||||||
|
# classes natives
|
||||||
|
self.assertIsNone(get_logger_name(str))
|
||||||
|
self.assertIsNone(get_logger_name(int))
|
||||||
|
self.assertIsNone(get_logger_name(dict))
|
||||||
|
self.assertIsNone(get_logger_name(list))
|
||||||
|
self.assertIsNone(get_logger_name(tuple))
|
||||||
|
|
||||||
|
# instances de classes natives (mais pas str)
|
||||||
|
self.assertIsNone(get_logger_name(1))
|
||||||
|
self.assertIsNone(get_logger_name({'a': 'b'}))
|
||||||
|
self.assertIsNone(get_logger_name(['c']))
|
||||||
|
self.assertIsNone(get_logger_name(('d',)))
|
||||||
|
|
||||||
|
# fonctions natives
|
||||||
|
self.assertIsNone(get_logger_name(str.join))
|
||||||
|
|
||||||
|
# str
|
||||||
|
self.assertEqual(get_logger_name('test'), 'test')
|
||||||
|
self.assertEqual(get_logger_name(''), None)
|
||||||
|
|
||||||
|
# classes
|
||||||
|
self.assertEqual(get_logger_name(MainType), main_type_logger)
|
||||||
|
self.assertEqual(get_logger_name(SubType), sub_type_logger)
|
||||||
|
|
||||||
|
# instances de classes
|
||||||
|
self.assertEqual(get_logger_name(main_type), main_type_logger)
|
||||||
|
self.assertEqual(get_logger_name(sub_type), sub_type_logger)
|
||||||
|
|
||||||
|
# fonctions et méthodes
|
||||||
|
self.assertEqual(get_logger_name(local_func), module_name)
|
||||||
|
self.assertEqual(get_logger_name(my_func), ext_module_name)
|
||||||
|
self.assertEqual(get_logger_name(MainType.class_met), main_type_logger)
|
||||||
|
self.assertEqual(get_logger_name(MainType.static_met), main_type_logger)
|
||||||
|
self.assertEqual(get_logger_name(main_type.std_met), main_type_logger)
|
||||||
|
self.assertEqual(get_logger_name(SubType.sub_class_met), sub_type_logger)
|
||||||
|
self.assertEqual(get_logger_name(SubType.sub_static_met), sub_type_logger)
|
||||||
|
self.assertEqual(get_logger_name(sub_type.sub_std_met), sub_type_logger)
|
||||||
|
|
||||||
|
def test_get_logger_without_tags(self):
|
||||||
|
func_1 = MyClass.class_met
|
||||||
|
func_2 = MyClass.static_met
|
||||||
|
|
||||||
|
def given():
|
||||||
|
self.assertEqual(get_logger_name(func_1), get_logger_name(func_2), 'le nom de logger doit être le même')
|
||||||
|
given()
|
||||||
|
|
||||||
|
msg = "l'instance doit être la même"
|
||||||
|
logger_name = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
|
||||||
|
self.assertIs(get_logger(logger_name), get_logger(logger_name), msg)
|
||||||
|
self.assertIs(get_logger(func_1), get_logger(func_2), msg)
|
||||||
|
|
||||||
|
def test_get_logger_with_tags(self):
|
||||||
|
func_1 = MyClass.class_met
|
||||||
|
func_2 = MyClass.static_met
|
||||||
|
|
||||||
|
def given():
|
||||||
|
self.assertEqual(get_logger_name(func_1), get_logger_name(func_2), 'le nom de logger doit être le même')
|
||||||
|
given()
|
||||||
|
|
||||||
|
msg = "l'instance doit être la même"
|
||||||
|
for tags in [TAG_PERF, TAG_DATA_FEED, ('1', '2'), ['3', '4'], set(['5', '6'])]:
|
||||||
|
with self.subTest(tags=tags):
|
||||||
|
logger_name = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
|
||||||
|
self.assertIs(get_logger(logger_name, tags), get_logger(logger_name, tags), msg)
|
||||||
|
self.assertIs(get_logger(func_1, tags), get_logger(func_2, tags), msg)
|
||||||
|
|
||||||
|
prev_logger = None
|
||||||
|
logger_name = ''.join(random.choice(string.ascii_lowercase) for i in range(10))
|
||||||
|
for i, tags in enumerate([('1', '2', '3', '4'), ['3', '4', '1', '2'], set(['2', '1', '3', '4', '1'])]):
|
||||||
|
curr_logger = get_logger(logger_name, tags)
|
||||||
|
if i > 0:
|
||||||
|
self.assertIs(curr_logger, prev_logger, msg)
|
||||||
|
prev_logger = get_logger(logger_name, tags)
|
||||||
127
backend-django/backend/tests/utils/predicates.py
Normal file
127
backend-django/backend/tests/utils/predicates.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
from .samples import my_func, _my_private_func, MyClass
|
||||||
|
from ...utils.predicates import func_class_name_is_in, func_class_name_is_not_in, func_name_is_in, func_name_is_not_in, func_is_public
|
||||||
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class PredicatesTest(SimpleTestCase):
|
||||||
|
|
||||||
|
def __test_func_class_name_is_in(self, negation: bool = False):
|
||||||
|
""" teste func_class_name_is_in et func_class_name_is_not_in """
|
||||||
|
|
||||||
|
func = func_class_name_is_not_in if negation else func_class_name_is_in
|
||||||
|
result = negation is not True
|
||||||
|
|
||||||
|
own_name = self.__class__.__name__
|
||||||
|
MainType = MyClass
|
||||||
|
main_type = MainType(11)
|
||||||
|
main_type_name = MainType.__name__
|
||||||
|
SubType = MyClass.MySubClass
|
||||||
|
sub_type = SubType(13)
|
||||||
|
sub_type_name = SubType.__name__
|
||||||
|
|
||||||
|
def local_func(a: int, b: int):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
# fonction native : None
|
||||||
|
self.assertEqual(func(str.__name__)(str.join), False)
|
||||||
|
|
||||||
|
# fonction locale : None
|
||||||
|
self.assertEqual(func(own_name)(local_func), False)
|
||||||
|
|
||||||
|
# méthodes
|
||||||
|
predicate = func(main_type_name)
|
||||||
|
self.assertEqual(predicate(MainType.class_met), result)
|
||||||
|
self.assertEqual(predicate(MainType.static_met), result)
|
||||||
|
self.assertEqual(predicate(main_type.std_met), result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_class_met), not result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_static_met), not result)
|
||||||
|
self.assertEqual(predicate(sub_type.sub_std_met), not result)
|
||||||
|
|
||||||
|
predicate = func(sub_type_name)
|
||||||
|
self.assertEqual(predicate(MainType.class_met), not result)
|
||||||
|
self.assertEqual(predicate(MainType.static_met), not result)
|
||||||
|
self.assertEqual(predicate(main_type.std_met), not result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_class_met), result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_static_met), result)
|
||||||
|
self.assertEqual(predicate(sub_type.sub_std_met), result)
|
||||||
|
|
||||||
|
def __test_func_name_is_in(self, negation: bool = False):
|
||||||
|
""" teste func_name_is_in et func_name_is_not_in """
|
||||||
|
|
||||||
|
func = func_name_is_not_in if negation else func_name_is_in
|
||||||
|
result = negation is not True
|
||||||
|
|
||||||
|
MainType = MyClass
|
||||||
|
main_type = MainType(11)
|
||||||
|
SubType = MyClass.MySubClass
|
||||||
|
sub_type = SubType(13)
|
||||||
|
|
||||||
|
def local_func(a: int, b: int):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
# fonction native
|
||||||
|
self.assertEqual(func('join')(str.join), result)
|
||||||
|
self.assertEqual(func('other', 'join')(str.join), result)
|
||||||
|
|
||||||
|
# autre fonction, fonction locale
|
||||||
|
self.assertEqual(func('local_func')(local_func), result)
|
||||||
|
self.assertEqual(func('other', 'local_func')(local_func), result)
|
||||||
|
self.assertEqual(func('my_func')(my_func), result)
|
||||||
|
self.assertEqual(func('other', 'my_func')(my_func), result)
|
||||||
|
|
||||||
|
|
||||||
|
# méthodes
|
||||||
|
predicate = func('class_met', 'std_met', 'sub_static_met')
|
||||||
|
self.assertEqual(predicate(MainType.class_met), result)
|
||||||
|
self.assertEqual(predicate(MainType.static_met), not result)
|
||||||
|
self.assertEqual(predicate(main_type.std_met), result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_class_met), not result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_static_met), result)
|
||||||
|
self.assertEqual(predicate(sub_type.sub_std_met), not result)
|
||||||
|
|
||||||
|
predicate = func('static_met', 'sub_class_met', 'sub_std_met')
|
||||||
|
self.assertEqual(predicate(MainType.class_met), not result)
|
||||||
|
self.assertEqual(predicate(MainType.static_met), result)
|
||||||
|
self.assertEqual(predicate(main_type.std_met), not result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_class_met), result)
|
||||||
|
self.assertEqual(predicate(SubType.sub_static_met), not result)
|
||||||
|
self.assertEqual(predicate(sub_type.sub_std_met), result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_func_class_name_is_in(self):
|
||||||
|
self.__test_func_class_name_is_in()
|
||||||
|
|
||||||
|
def test_func_class_name_is_not_in(self):
|
||||||
|
self.__test_func_class_name_is_in(negation=True)
|
||||||
|
|
||||||
|
def test_func_name_is_in(self):
|
||||||
|
self.__test_func_name_is_in()
|
||||||
|
|
||||||
|
def test_func_name_is_not_in(self):
|
||||||
|
self.__test_func_name_is_in(negation=True)
|
||||||
|
|
||||||
|
def test_func_is_public(self):
|
||||||
|
MainType = MyClass
|
||||||
|
main_type = MainType(11)
|
||||||
|
SubType = MyClass.MySubClass
|
||||||
|
sub_type = SubType(13)
|
||||||
|
|
||||||
|
def local_func(a: int, b: int):
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
# fonction native
|
||||||
|
self.assertEqual(func_is_public(str.join), True)
|
||||||
|
|
||||||
|
# autre fonction, fonction locale
|
||||||
|
self.assertEqual(func_is_public(local_func), True)
|
||||||
|
self.assertEqual(func_is_public(my_func), True)
|
||||||
|
self.assertEqual(func_is_public(_my_private_func), False)
|
||||||
|
|
||||||
|
# méthodes
|
||||||
|
self.assertEqual(func_is_public(MainType._protected_met), False)
|
||||||
|
self.assertEqual(func_is_public(MainType.class_met), True)
|
||||||
|
self.assertEqual(func_is_public(MainType.static_met), True)
|
||||||
|
self.assertEqual(func_is_public(main_type.std_met), True)
|
||||||
|
self.assertEqual(func_is_public(SubType.sub_class_met), True)
|
||||||
|
self.assertEqual(func_is_public(SubType.sub_static_met), True)
|
||||||
|
self.assertEqual(func_is_public(sub_type.sub_std_met), True)
|
||||||
58
backend-django/backend/tests/utils/samples.py
Normal file
58
backend-django/backend/tests/utils/samples.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# quelques définitions pour les tests ==>
|
||||||
|
|
||||||
|
def my_func(a: int, b: int):
|
||||||
|
""" fonction sans classe """
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
|
||||||
|
def _my_private_func(a: int, b: int):
|
||||||
|
""" fonction sans classe (non importée par défaut avec import *) """
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
|
||||||
|
class MyClass():
|
||||||
|
""" classe pour tests """
|
||||||
|
num = None
|
||||||
|
|
||||||
|
def __init__(self, num):
|
||||||
|
""" la classe est paramétrée """
|
||||||
|
self.num = num
|
||||||
|
|
||||||
|
def _protected_met(self, a: int, b: int):
|
||||||
|
""" méthode standard (protected par convention) """
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
class MySubClass():
|
||||||
|
sub_num = None
|
||||||
|
|
||||||
|
def __init__(self, num):
|
||||||
|
""" la classe est paramétrée """
|
||||||
|
self.sub_num = num
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sub_class_met(cls):
|
||||||
|
""" méthode de classe """
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sub_static_met():
|
||||||
|
""" méthode statique """
|
||||||
|
return True
|
||||||
|
|
||||||
|
def sub_std_met():
|
||||||
|
""" méthode standard """
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def class_met(cls, a: int, b: int):
|
||||||
|
""" méthode de classe """
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def static_met(a: int, b: int):
|
||||||
|
""" méthode statique """
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def std_met(self, a: int, b: int):
|
||||||
|
""" méthode standard """
|
||||||
|
return a + b
|
||||||
2
backend-django/backend/tests/views/__init__.py
Normal file
2
backend-django/backend/tests/views/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .decision import *
|
||||||
|
from .notation import *
|
||||||
2
backend-django/backend/tests/views/constants.py
Normal file
2
backend-django/backend/tests/views/constants.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
USERNAME = 'test'
|
||||||
|
PASSWORD = 'test'
|
||||||
147
backend-django/backend/tests/views/decision.py
Normal file
147
backend-django/backend/tests/views/decision.py
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from backend.models import Administre, Decision, DecisionChoices, Poste
|
||||||
|
from backend.models import StatutPamChoices as StatutPam
|
||||||
|
from backend.utils.decisions import (KEY_CREATE, KEY_UPDATE,
|
||||||
|
get_available_decisions)
|
||||||
|
from backend.utils.permissions import (KEY_READ, KEY_WRITE, Profiles,
|
||||||
|
get_profiles_by_adm)
|
||||||
|
from backend.views import DecisionView
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from .constants import PASSWORD, USERNAME
|
||||||
|
from .test_utils import TestUtilsMixin, disable_gestionnaire_permission
|
||||||
|
|
||||||
|
PK_SV_1 = 'SV1'
|
||||||
|
VIEW_TYPE = DecisionView
|
||||||
|
|
||||||
|
class DecisionViewTest(APITestCase, TestUtilsMixin):
|
||||||
|
|
||||||
|
basename = 'Decision'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
user = get_user_model().objects.create(id=1, username=USERNAME, is_superuser=True)
|
||||||
|
user.set_password(PASSWORD)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
""" vérifie qu'il n'existe aucun administré ou poste avant le test """
|
||||||
|
|
||||||
|
self.assertEqual(Administre.objects.exists(), False, "pas d'administré avant un test")
|
||||||
|
self.assertEqual(Poste.objects.exists(), False, "pas de poste avant un test")
|
||||||
|
logged_in = self.client.login(username=USERNAME, password=PASSWORD)
|
||||||
|
self.assertTrue(logged_in, "l'utilisateur devrait être connecté")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
""" supprime tous les administrés, les postes, les décisions en fin de test """
|
||||||
|
|
||||||
|
self.client.logout()
|
||||||
|
Decision.objects.all().delete()
|
||||||
|
Poste.objects.all().delete()
|
||||||
|
Administre.objects.all().delete()
|
||||||
|
|
||||||
|
def test_list_anonymous(self):
|
||||||
|
""" vérifie que l'accès anonyme est interdit """
|
||||||
|
|
||||||
|
self.client.logout()
|
||||||
|
url = self._api_url()
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
@disable_gestionnaire_permission(VIEW_TYPE)
|
||||||
|
def test_list_authenticated(self):
|
||||||
|
""" vérifie que l'utilisateur authentifié peut récupérer des décisions """
|
||||||
|
|
||||||
|
url = self._api_url()
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@disable_gestionnaire_permission(VIEW_TYPE)
|
||||||
|
def test_crud(self):
|
||||||
|
""" test de création, MAJ, suppression """
|
||||||
|
try:
|
||||||
|
statut_pam = next(x for x in StatutPam if x.dec_enabled)
|
||||||
|
self.assertIsNotNone(statut_pam, 'il devrait exister un statut permettant les décisions')
|
||||||
|
self.user = get_user_model().objects.first()
|
||||||
|
self.adm = Administre.objects.create(pk=1, a_statut_pam=statut_pam)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
f'{get_available_decisions.__module__}.{get_profiles_by_adm.__name__}',
|
||||||
|
return_value={self.adm.pk: {KEY_READ: (), KEY_WRITE: (Profiles.FILIERE, Profiles.PCP)}}
|
||||||
|
)
|
||||||
|
def do_with_mock_profiles(mock):
|
||||||
|
user = self.user
|
||||||
|
adm = self.adm
|
||||||
|
decisions = get_available_decisions((adm,), user=user).get(adm.pk)
|
||||||
|
self.assertTrue(decisions.get(KEY_CREATE), 'il devrait exister des décisions possibles (création)')
|
||||||
|
|
||||||
|
def create(poste_id, de_decision, adm_id=adm.pk, delete_former=None):
|
||||||
|
return self.client.post(
|
||||||
|
self._api_url(),
|
||||||
|
{'administre_id': adm_id, 'poste_id': poste_id, 'de_decision': de_decision, **({'delete_former': delete_former} if isinstance(delete_former, bool) else {})}
|
||||||
|
)
|
||||||
|
|
||||||
|
postes = (Poste.objects.create(pk='11'), Poste.objects.create(pk='13'))
|
||||||
|
poste_1 = postes[0].pk
|
||||||
|
poste_2 = postes[1].pk
|
||||||
|
dec_status_1 = decisions.get(KEY_CREATE)[0]
|
||||||
|
|
||||||
|
# création initiale
|
||||||
|
response = create(poste_1, dec_status_1)
|
||||||
|
|
||||||
|
qs_decision1 = Decision.objects.filter(pk=adm.pk, poste_id=poste_1, de_decision=dec_status_1)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||||
|
self.assertTrue(qs_decision1.exists(), "la décision n'a pas les bonnes données")
|
||||||
|
|
||||||
|
# création bis, pas possible sans forcer
|
||||||
|
response = create(poste_2, dec_status_1)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||||
|
self.assertTrue(qs_decision1.exists(), "la première décision doit encore exister")
|
||||||
|
|
||||||
|
# création bis en forçant
|
||||||
|
notes = 'notes'
|
||||||
|
qs_decision1.update(de_notes_gestionnaire='notes')
|
||||||
|
self.assertEqual(notes, qs_decision1.first().de_notes_gestionnaire, "les notes doivent être sauvegardées pour le prochain test")
|
||||||
|
|
||||||
|
response = create(poste_2, dec_status_1, delete_former=True)
|
||||||
|
|
||||||
|
qs_decision2 = Decision.objects.filter(pk=adm.pk, poste_id=poste_2, de_decision=dec_status_1)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||||
|
self.assertFalse(qs_decision1.exists(), "la première décision ne doit plus exister")
|
||||||
|
self.assertTrue(qs_decision2.exists(), "la deuxième décision n'a pas les bonnes données")
|
||||||
|
self.assertIsNone(qs_decision2.first().de_notes_gestionnaire, "il ne doit plus exister de notes dans la deuxième décision")
|
||||||
|
|
||||||
|
# MAJ
|
||||||
|
adm = Administre.objects.filter(pk=adm.pk).first()
|
||||||
|
decisions = get_available_decisions((adm,), user=user).get(adm.pk)
|
||||||
|
self.assertTrue(decisions.get(KEY_UPDATE), 'il devrait exister des décisions possibles (MAJ) pour le prochain test')
|
||||||
|
dec_status_2 = decisions.get(KEY_UPDATE)[0]
|
||||||
|
|
||||||
|
response = self.client.patch(self._api_url(adm.pk), data={'de_decision': dec_status_2})
|
||||||
|
|
||||||
|
qs_decision3 = Decision.objects.filter(pk=adm.pk, poste_id=poste_2, de_decision=dec_status_2)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(1, Decision.objects.count(), "il doit exister une seule décision")
|
||||||
|
self.assertTrue(qs_decision3.exists(), "la deuxième décision doit changer de statut")
|
||||||
|
|
||||||
|
# suppression
|
||||||
|
response = self.client.delete(self._api_url(adm.pk))
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
self.assertEqual(0, Decision.objects.count(), "il ne doit plus exister de décision")
|
||||||
|
|
||||||
|
do_with_mock_profiles()
|
||||||
|
finally:
|
||||||
|
self.user = None
|
||||||
|
self.adm = None
|
||||||
46
backend-django/backend/tests/views/notation.py
Normal file
46
backend-django/backend/tests/views/notation.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from backend.views import NotationView
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
from .constants import PASSWORD, USERNAME
|
||||||
|
from .test_utils import TestUtilsMixin, disable_gestionnaire_permission
|
||||||
|
|
||||||
|
VIEW_TYPE = NotationView
|
||||||
|
|
||||||
|
class NotationViewTest(APITestCase, TestUtilsMixin):
|
||||||
|
|
||||||
|
basename = 'Notation'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
user = get_user_model().objects.create(id=1, username=USERNAME)
|
||||||
|
user.set_password(PASSWORD)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
logged_in = self.client.login(username=USERNAME, password=PASSWORD)
|
||||||
|
self.assertTrue(logged_in, "l'utilisateur devrait être connecté")
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.client.logout()
|
||||||
|
|
||||||
|
def test_list_anonymous(self):
|
||||||
|
""" vérifie que l'accès anonyme est interdit """
|
||||||
|
|
||||||
|
self.client.logout()
|
||||||
|
url = self._api_url()
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
@disable_gestionnaire_permission(VIEW_TYPE)
|
||||||
|
def test_list_authenticated(self):
|
||||||
|
""" vérifie que l'utilisateur authentifié peut récupérer des décisions """
|
||||||
|
|
||||||
|
url = self._api_url()
|
||||||
|
|
||||||
|
response = self.client.get(url)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
44
backend-django/backend/tests/views/test_utils.py
Normal file
44
backend-django/backend/tests/views/test_utils.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from django.urls import reverse
|
||||||
|
from typing import Any
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
|
||||||
|
def disable_gestionnaire_permission(type):
|
||||||
|
"""
|
||||||
|
Utilise "mock" pour ignorer le gestionnaire de permissions dans la vue.
|
||||||
|
TODO trouver un mécanisme de mock plus précis
|
||||||
|
|
||||||
|
:param type: le type de la vue
|
||||||
|
:type type: class:`ModelViewSet` par exemple
|
||||||
|
|
||||||
|
:return: résultat du mock
|
||||||
|
:rtype: voir mock
|
||||||
|
"""
|
||||||
|
return mock.patch.object(type, 'permission_classes', [p for p in type.permission_classes if p.__name__ != 'GestionnairePermission'])
|
||||||
|
|
||||||
|
|
||||||
|
def api_url(basename: str, pk: Any = None) -> str:
|
||||||
|
"""
|
||||||
|
renvoie l'URL selon qu'on a besoin d'un ID ou non
|
||||||
|
nécessite l'utilisation de SimpleRouter ou DefaultRouter (voir urls.py)
|
||||||
|
|
||||||
|
:param basename: valeur de "basename"
|
||||||
|
:type basename: str
|
||||||
|
|
||||||
|
:param pk: valeur qui permet de savoir s'il s'agit d'une URL "list" ou "detail" (voir https://www.django-rest-framework.org/api-guide/routers/#simplerouter)
|
||||||
|
:type pk: Any
|
||||||
|
|
||||||
|
:return: URL
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return reverse(f'{basename}-list') if pk is None else reverse(f'{basename}-detail', args=[pk])
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtilsMixin:
|
||||||
|
"""
|
||||||
|
mixin à ajouter pour les tests
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _api_url(self, pk: Any = None) -> str:
|
||||||
|
""" voir fonction "api_url", nécessite un champ "basename" pour remplir le paramètre """
|
||||||
|
return api_url(self.basename, pk)
|
||||||
48
backend-django/backend/urls.py
Normal file
48
backend-django/backend/urls.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
""" Ce fichier est pour organiser les URLs de l'application web. Le code de ce module est une correspondance entre expressions de chemins d’URL et fonctions Python.
|
||||||
|
"""
|
||||||
|
from django.urls import include, path
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
router.register(r'notations', views.NotationView, basename='Notation')
|
||||||
|
router.register(r'decisions', views.DecisionView, basename='Decision')
|
||||||
|
router.register(r'groupes_marques', views.MarquesGroupeView, basename='MarquesGroupe')
|
||||||
|
router.register(r'marques', views.MarqueView, basename='Marque')
|
||||||
|
router.register(r'domaines', views.DomaineView, basename='Domaine')
|
||||||
|
router.register(r'filieres', views.FiliereView, basename='Filiere')
|
||||||
|
router.register(r'administres', views.AdministreView, basename='Administre')
|
||||||
|
router.register(r'administres_pams', views.AdministrePAMView, basename='Administre')
|
||||||
|
router.register(r'postes', views.PosteView, basename='Poste')
|
||||||
|
router.register(r'postes_pams', views.PostePAMView, basename='Poste')
|
||||||
|
router.register(r'liste_preferences', views.ListesPreferencesView, basename='PreferencesListe')
|
||||||
|
router.register(r'fmob', views.FmobView, basename='FMOB')
|
||||||
|
router.register(r'formation_emploi', views.FormationEmploiView, basename='FormationEmploi')
|
||||||
|
router.register(r'sous_vivier_association', views.SousVivierAssociationView, basename='SousVivierAssociation')
|
||||||
|
router.register(r'fe_pcp', views.PcpFeGroupeView, basename='PcpFeGroupe')
|
||||||
|
# Wire up our API using automatic URL routing.
|
||||||
|
# Additionally, we include login URLs for the browsable API.
|
||||||
|
urlpatterns = [
|
||||||
|
path('alimentation/', views.AlimentationView.as_view()),
|
||||||
|
path('nettoyage_pam/', views.NettoyagePamView.as_view()),
|
||||||
|
path('chargement_pam/', views.AlimentationPamView.as_view()),
|
||||||
|
path('alimentation_ref_fe/', views.AlimentationReferentielView.as_view()),
|
||||||
|
path('alimentation_zones_geo/', views.AlimentationZoneGeographiqueView.as_view()),
|
||||||
|
path('alimentation_ref_droits/', views.AlimentationReferentielsDroitView.as_view()),
|
||||||
|
path('alimentation_commentaires/', views.AlimentationCommentairesView.as_view()),
|
||||||
|
path('suppression_administres/', views.SuppressionAdministresView.as_view()),
|
||||||
|
path('exportation_fichiers/', views.ExportationFichiersView.as_view()),
|
||||||
|
path('scoring/', views.ScoringView.as_view()),
|
||||||
|
path('arret/', views.ArretCalcul.as_view()),
|
||||||
|
path('suppression/', views.SuppressionAdministresView.as_view()),
|
||||||
|
path('references/', views.ReferencesView.as_view()),
|
||||||
|
path('me/', views.CurrentUserView.as_view()),
|
||||||
|
path('auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||||
|
path('chargement_sv/', views.ChargementSVView.as_view()),
|
||||||
|
path('chargement_competences/', views.ChargementCompetenceView.as_view()),
|
||||||
|
path('fiche_detaillee/', views.FicheDetailleeView.as_view()),
|
||||||
|
path('reporting/', views.ReportingView.as_view()),
|
||||||
|
path('', include(router.urls)),
|
||||||
|
|
||||||
|
]
|
||||||
155
backend-django/backend/utils.py
Normal file
155
backend-django/backend/utils.py
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
"""
|
||||||
|
Ce module contient les Utilitaires du backend
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
from random import randint
|
||||||
|
import re
|
||||||
|
from django.forms import model_to_dict
|
||||||
|
|
||||||
|
from backend import constants
|
||||||
|
from backend.models import Administre, Poste, SousVivier, FormationEmploi
|
||||||
|
|
||||||
|
|
||||||
|
def check_positive(valeur_nb):
|
||||||
|
"""
|
||||||
|
Vérifier si une valeur est positive et retourner 1, si elle est nulle ou négative retourner 0.
|
||||||
|
|
||||||
|
:type poste_nb: DataFrame
|
||||||
|
:param poste_nb: ID du sous-vivier
|
||||||
|
|
||||||
|
|
||||||
|
:return: - **valeur_nb_modifie** (*dataframe*): Dataframe contenant des valeurs 1 ou 0.
|
||||||
|
"""
|
||||||
|
valeur_nb_modifie = valeur_nb.apply(lambda x: 1 if x > 0 else 0)
|
||||||
|
return valeur_nb_modifie
|
||||||
|
|
||||||
|
def cleanString(string):
|
||||||
|
"""Cette fonction supprimera tous les caractères qui ne sont pas alphanumériques.
|
||||||
|
|
||||||
|
:type string: chaîne de caractères
|
||||||
|
:param string: chaîne qui doit être nettoyée
|
||||||
|
|
||||||
|
:return: - **return** (*chaîne de caractères*): chaîne nettoyée.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# print(string)
|
||||||
|
return ''.join([i for i in string if i.isalnum()])
|
||||||
|
|
||||||
|
|
||||||
|
def intOrNone(value):
|
||||||
|
"""Cette fonction renvoie l'entier de la valeur ou renvoie None.
|
||||||
|
|
||||||
|
:type value: float
|
||||||
|
:param value: valeur a con
|
||||||
|
|
||||||
|
:return: - **res** (*int or None*): valeur à convertir.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
res = int(float(value))
|
||||||
|
except:
|
||||||
|
res = None
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def impact_decisions(old_administre, administre, old_avis, avis, eip, fe_code, categorie):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:type administre: objet
|
||||||
|
:param administre: instance du model Administre
|
||||||
|
|
||||||
|
:type old_avis: chaine de caractères
|
||||||
|
:param old_avis: avis de l'administré avant la mise à jour
|
||||||
|
|
||||||
|
:type avis: chaine de caractère
|
||||||
|
:param avis: nouvel avis de l'administré
|
||||||
|
|
||||||
|
:type eip: chaine de caractère
|
||||||
|
:param eip: eip actuel du militaire
|
||||||
|
|
||||||
|
:type categorie: dataframe pandas
|
||||||
|
:param categorie: Dataframe contenant les données pretraités à inserer
|
||||||
|
|
||||||
|
:return: - **list_error** (*liste*): liste des identifiants SAP pour lesquels il y a eu une erreur.
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
list_error = []
|
||||||
|
if (old_avis == 'NON_ETUDIE' or old_avis == 'A_MAINTENIR' or
|
||||||
|
old_avis == 'A_ETUDIER') and (
|
||||||
|
avis == 'A_MUTER' or avis == 'PARTANT' or avis == 'NON_DISPONIBLE'):
|
||||||
|
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code).exclude(
|
||||||
|
p_nb_occupe=0)
|
||||||
|
poste = poste_qs.first()
|
||||||
|
print(fe_code)
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||||
|
if categorie == constants.CATEGORIE_MDR:
|
||||||
|
fe.fe_nb_poste_vacant_mdr = fe.fe_nb_poste_vacant_mdr + 1
|
||||||
|
fe.fe_nb_poste_occupe_mdr = fe.fe_nb_poste_occupe_mdr - 1
|
||||||
|
elif categorie == constants.CATEGORIE_SOFF:
|
||||||
|
fe.fe_nb_poste_vacant_soff = fe.fe_nb_poste_vacant_soff + 1
|
||||||
|
fe.fe_nb_poste_occupe_soff = fe.fe_nb_poste_occupe_soff - 1
|
||||||
|
elif categorie == constants.CATEGORIE_OFF:
|
||||||
|
fe.fe_nb_poste_vacant_off = fe.fe_nb_poste_vacant_off + 1
|
||||||
|
fe.fe_nb_poste_occupe_off = fe.fe_nb_poste_occupe_off - 1
|
||||||
|
|
||||||
|
fe.save()
|
||||||
|
|
||||||
|
|
||||||
|
if poste and poste.p_nb_non_etudie > 0:
|
||||||
|
print(poste)
|
||||||
|
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 + 1, poste.p_nb_non_etudie - 1, poste.p_nb_vacant + 1, poste.p_nb_occupe - 1
|
||||||
|
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
|
||||||
|
else:
|
||||||
|
list_error.append(administre.a_id_sap)
|
||||||
|
|
||||||
|
if (old_avis == 'A_MUTER' or old_avis == 'PARTANT' or
|
||||||
|
old_avis == 'NON_DISPONIBLE') and (
|
||||||
|
avis == 'NON_ETUDIE' or avis == 'A_MAINTENIR' or avis == 'A_ETUDIER'):
|
||||||
|
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code).exclude(p_nb_occupe=0)
|
||||||
|
poste = poste_qs.first()
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||||
|
if categorie == constants.CATEGORIE_MDR:
|
||||||
|
fe.fe_nb_poste_vacant_mdr = fe.fe_nb_poste_vacant_mdr - 1
|
||||||
|
fe.fe_nb_poste_occupe_mdr = fe.fe_nb_poste_occupe_mdr + 1
|
||||||
|
elif categorie == constants.CATEGORIE_SOFF:
|
||||||
|
fe.fe_nb_poste_vacant_soff = fe.fe_nb_poste_vacant_soff - 1
|
||||||
|
fe.fe_nb_poste_occupe_soff = fe.fe_nb_poste_occupe_soff + 1
|
||||||
|
elif categorie == constants.CATEGORIE_OFF:
|
||||||
|
fe.fe_nb_poste_vacant_off = fe.fe_nb_poste_vacant_off - 1
|
||||||
|
fe.fe_nb_poste_occupe_off = fe.fe_nb_poste_occupe_off + 1
|
||||||
|
|
||||||
|
fe.save()
|
||||||
|
print(model_to_dict(fe))
|
||||||
|
|
||||||
|
if poste and poste.p_nb_p4 > 0:
|
||||||
|
print(poste)
|
||||||
|
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 > 0:
|
||||||
|
new_nb_p3, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p3 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p3=new_nb_p3, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 > 0:
|
||||||
|
new_nb_p2, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p2 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p2=new_nb_p2, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 == 0 and poste.p_nb_p1 > 0:
|
||||||
|
new_nb_p1, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p1 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p1=new_nb_p1, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
|
||||||
|
else:
|
||||||
|
list_error.append(administre.a_id_sap)
|
||||||
|
|
||||||
|
print(list_error)
|
||||||
|
return list_error
|
||||||
12
backend-django/backend/utils/__init__.py
Normal file
12
backend-django/backend/utils/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from .alimentation import *
|
||||||
|
from .alimentation_decorators import *
|
||||||
|
from .decisions import *
|
||||||
|
from .decorators import *
|
||||||
|
from .extraction import *
|
||||||
|
from .functions import *
|
||||||
|
from .initial import *
|
||||||
|
from .insertion import *
|
||||||
|
from .logging import *
|
||||||
|
from .permissions import *
|
||||||
|
from .predicates import *
|
||||||
|
from .view_predicates import *
|
||||||
259
backend-django/backend/utils/alimentation.py
Normal file
259
backend-django/backend/utils/alimentation.py
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import datetime
|
||||||
|
from enum import Enum, EnumMeta, unique
|
||||||
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def excel_date_converter(value) -> Union[str, float]:
|
||||||
|
"""
|
||||||
|
convertit la valeur en date (sans information de temps)
|
||||||
|
note : type(np.nan) => <class 'float'>
|
||||||
|
"""
|
||||||
|
if isinstance(value, datetime.datetime):
|
||||||
|
return value.date().isoformat()
|
||||||
|
if isinstance(value, str):
|
||||||
|
val = '-'.join(list(reversed(value.split('/'))))
|
||||||
|
return datetime.datetime.strptime(val, "%Y-%m-%d").date().isoformat() if val else np.nan
|
||||||
|
return str(value) if value else np.nan
|
||||||
|
|
||||||
|
|
||||||
|
def excel_int64_converter(value) -> Union[int, float]:
|
||||||
|
"""
|
||||||
|
convertit la valeur en entier
|
||||||
|
note : type(np.nan) => <class 'float'>
|
||||||
|
"""
|
||||||
|
if isinstance(value, int):
|
||||||
|
return value
|
||||||
|
val = value.strip() if isinstance(value, str) else value
|
||||||
|
return np.int64(val) if val else np.nan
|
||||||
|
|
||||||
|
|
||||||
|
def excel_uint64_converter(value) -> Union[int, float]:
|
||||||
|
"""
|
||||||
|
convertit la valeur en entier non signé
|
||||||
|
note : type(np.nan) => <class 'float'>
|
||||||
|
"""
|
||||||
|
if isinstance(value, int):
|
||||||
|
return value
|
||||||
|
val = value.strip() if isinstance(value, str) else value
|
||||||
|
return np.uint64(val) if val else np.nan
|
||||||
|
|
||||||
|
|
||||||
|
class FileCols(str, Enum):
|
||||||
|
""" Classe de base pour les colonnes d'un fichier """
|
||||||
|
|
||||||
|
converter: str
|
||||||
|
|
||||||
|
def __new__(metacls, value: str, converter=None):
|
||||||
|
obj = str.__new__(metacls, value)
|
||||||
|
obj._value_ = value
|
||||||
|
obj.converter = converter
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def columns(cls, *args: 'FileCols') -> List[str]:
|
||||||
|
"""
|
||||||
|
Renvoie les noms de colonnes. Deux cas :
|
||||||
|
- il n'y a pas d'argument : tous les noms de colonnes
|
||||||
|
- il y a des arguments : uniquement leurs noms de colonnes
|
||||||
|
|
||||||
|
:param args: membres de l'enum
|
||||||
|
:type args: class:`FileCols` (multiple)
|
||||||
|
|
||||||
|
:return: noms de colonnes
|
||||||
|
:rtype: List[str]
|
||||||
|
"""
|
||||||
|
return [member.value for member in (args or cls)]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def col_mapping(cls, mapping: Dict[Union[str, 'FileCols'], Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Renvoie un mapping de noms pour utiliser avec pandas.DataFrame.rename.
|
||||||
|
Les clés du dictionnaire initial peut être des membres de l'enum, elles seront converties
|
||||||
|
en noms de colonnes.
|
||||||
|
|
||||||
|
:param mapping: correspondances initiales
|
||||||
|
:type mapping: Dict[Union[str, FileCols], Any]
|
||||||
|
|
||||||
|
:return: nouvelles correspondances
|
||||||
|
:rtype: Dict[str, Any]
|
||||||
|
"""
|
||||||
|
return {k.value if isinstance(k, cls) else k: v for k, v in mapping.items()}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def converters(cls, *args: 'FileCols') -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Renvoie un mapping de conversion pour Pandas. Deux cas :
|
||||||
|
- il n'y a pas d'argument : mapping de toutes les colonnes qui ont un convertisseur explicite
|
||||||
|
- il y a des arguments : mapping des colonnes fournies qui ont un convertisseur explicite
|
||||||
|
|
||||||
|
:return: mapping entre noms de colonnes et convertisseur
|
||||||
|
:rtype: Dict[str, Any]
|
||||||
|
"""
|
||||||
|
return {member.value: member.converter for member in (args or cls) if member.converter is not None}
|
||||||
|
|
||||||
|
|
||||||
|
class BOCols(FileCols):
|
||||||
|
""" Colonnes du fichier BO """
|
||||||
|
|
||||||
|
AFFECTATION_1 = ('Affectation -1 L', str) # AV
|
||||||
|
AFFECTATION_2 = ('Affectation -2 L', str) # AX
|
||||||
|
AFFECTATION_3 = ('Affectation -3 L', str) # AZ
|
||||||
|
AFFECTATION_4 = ('Affectation -4 L', str) # BB
|
||||||
|
AFFECTATION_5 = ('Affectation -5 L', str) # BD
|
||||||
|
AFFECTATION_6 = ('Affectation -6 L', str) # BF
|
||||||
|
AFFECTATION_7 = ('Affectation -7 L', str) # BH
|
||||||
|
AFFECTATION_8 = ('Affectation -8 L', str) # BJ
|
||||||
|
AFFECTATION_9 = ('Affectation -9 L', str) # BL
|
||||||
|
AGE_ANNEES = ('Age en années (au 31/12)', excel_uint64_converter) # CG
|
||||||
|
ANNEE_NOTATION = ('Année notation A', excel_uint64_converter) # CL
|
||||||
|
ANNEE_NOTATION_1 = ('Année notation A-1', excel_uint64_converter) # CR
|
||||||
|
ANNEE_NOTATION_2 = ('Année notation A-2', excel_uint64_converter) # CX
|
||||||
|
ANNEE_NOTATION_3 = ('Année notation A-3', excel_uint64_converter) # DD
|
||||||
|
ANNEE_NOTATION_4 = ('Année notation A-4', excel_uint64_converter) # DJ
|
||||||
|
ANNEE_NOTATION_5 = ('Année notation A-5', excel_uint64_converter) # DP
|
||||||
|
APTITUDE_EMPLOI_SUP = ('Apt resp / Emp sup A', str) # CP
|
||||||
|
APTITUDE_EMPLOI_SUP_1 = ('Apt resp / Emp sup A-1', str) # CV
|
||||||
|
APTITUDE_EMPLOI_SUP_2 = ('Apt resp / Emp sup A-2', str) # DB
|
||||||
|
APTITUDE_EMPLOI_SUP_3 = ('Apt resp / Emp sup A-3', str) # DH
|
||||||
|
APTITUDE_EMPLOI_SUP_4 = ('Apt resp / Emp sup A-4', str) # DN
|
||||||
|
APTITUDE_EMPLOI_SUP_5 = ('Apt resp / Emp sup A-5', str) # DT
|
||||||
|
ARME = ('Arme', str) # L
|
||||||
|
CREDO_FE = ('CREDO FE act', str) # X
|
||||||
|
DATE_AFFECTATION_1 = ('Affectation -1 DD', excel_date_converter) # AU
|
||||||
|
DATE_AFFECTATION_2 = ('Affectation -2 DD', excel_date_converter) # AW
|
||||||
|
DATE_AFFECTATION_3 = ('Affectation -3 DD', excel_date_converter) # AY
|
||||||
|
DATE_AFFECTATION_4 = ('Affectation -4 DD', excel_date_converter) # BA
|
||||||
|
DATE_AFFECTATION_5 = ('Affectation -5 DD', excel_date_converter) # BC
|
||||||
|
DATE_AFFECTATION_6 = ('Affectation -6 DD', excel_date_converter) # BE
|
||||||
|
DATE_AFFECTATION_7 = ('Affectation -7 DD', excel_date_converter) # BG
|
||||||
|
DATE_AFFECTATION_8 = ('Affectation -8 DD', excel_date_converter) # BI
|
||||||
|
DATE_AFFECTATION_9 = ('Affectation -9 DD', excel_date_converter) # BK
|
||||||
|
DATE_ARRIVEE_FE = ('Date arrivée FE', excel_date_converter) # AA
|
||||||
|
DATE_DEBUT_GRADE = ('Grade act DD', excel_date_converter) # AH
|
||||||
|
DATE_DERNIER_ACR = ('Dernière mutation ACR D', excel_date_converter) # AC
|
||||||
|
DATE_ENTREE_SERVICE = ('Entrée en Service', excel_date_converter) # N
|
||||||
|
DATE_FONCTION_1 = ('Fonction -1 DD', excel_date_converter) # BM
|
||||||
|
DATE_FONCTION_2 = ('Fonction -2 DD', excel_date_converter) # BO
|
||||||
|
DATE_FONCTION_3 = ('Fonction -3 DD', excel_date_converter) # BQ
|
||||||
|
DATE_FONCTION_4 = ('Fonction -4 DD', excel_date_converter) # BS
|
||||||
|
DATE_FONCTION_5 = ('Fonction -5 DD', excel_date_converter) # BU
|
||||||
|
DATE_FONCTION_6 = ('Fonction -6 DD', excel_date_converter) # BW
|
||||||
|
DATE_FONCTION_7 = ('Fonction -7 DD', excel_date_converter) # BY
|
||||||
|
DATE_FONCTION_8 = ('Fonction -8 DD', excel_date_converter) # CA
|
||||||
|
DATE_FONCTION_9 = ('Fonction -9 DD', excel_date_converter) # CC
|
||||||
|
DATE_FUD = ('Date prise effet FUD départ', excel_date_converter) # DV
|
||||||
|
DATE_LIEN_SERVICE = ('Lien au service DF', excel_date_converter) # EG
|
||||||
|
DATE_NAISSANCE = ('Naissance', excel_date_converter) # R
|
||||||
|
DATE_POSITION_STATUAIRE = ('Date Position statutaire', excel_date_converter) # AF
|
||||||
|
DATE_RDC = ('Date Radiation des contrôles', excel_date_converter) # W
|
||||||
|
DATE_STATUT_CONCERTO = ('Situation administrative act DD', excel_date_converter) # DX
|
||||||
|
DATE_STATUT_CONCERTO_FUTUR = ('Date Position statu future', excel_date_converter) # DZ
|
||||||
|
DATE_MARIAGE = ('Situation familiale (IT0002) DD', excel_date_converter) # CH
|
||||||
|
DERNIER_DIPLOME = ('Dernier diplôme militaire LA', str) # V
|
||||||
|
DIPLOME_PLUS_HAUT_NIVEAU = ('Diplôme militaire de plus haut niveau', str) # U
|
||||||
|
DOMAINE = ('Domaine EIP act LA', str) # I
|
||||||
|
DOMAINE_GESTION = ('Domaine de gestion act LA', str) # H
|
||||||
|
DOMAINE_POSTE = ('Domaine emploi occupé act LA', str) # ED
|
||||||
|
EIP = ('EIP act LA', str) # F
|
||||||
|
EIS = ('EIS act LA', str) # G
|
||||||
|
ENFANTS = ('Enfants', str) # EK
|
||||||
|
FILIERE = ('Filière EIP act LA', str) # J
|
||||||
|
FILIERE_POSTE = ('Nature de filière emploi occupé act LA', str) # EE
|
||||||
|
FONCTION = ('Fonction act L', str) # T
|
||||||
|
FONCTION_1 = ('Fonction -1 L', str) # BN
|
||||||
|
FONCTION_2 = ('Fonction -2 L', str) # BP
|
||||||
|
FONCTION_3 = ('Fonction -3 L', str) # BR
|
||||||
|
FONCTION_4 = ('Fonction -4 L', str) # BT
|
||||||
|
FONCTION_5 = ('Fonction -5 L', str) # BV
|
||||||
|
FONCTION_6 = ('Fonction -6 L', str) # BX
|
||||||
|
FONCTION_7 = ('Fonction -7 L', str) # BZ
|
||||||
|
FONCTION_8 = ('Fonction -8 L', str) # CB
|
||||||
|
FONCTION_9 = ('Fonction -9 L', str) # CD
|
||||||
|
FUD = ('Type de FUD départ') # DW
|
||||||
|
GARNISON = ('Garnison act', str) # Z
|
||||||
|
GRADE = ('GRADE TA', str) # B
|
||||||
|
ID_DEF = ('Identifiant défense', str) # E
|
||||||
|
ID_DEF_CONJOINT = ('Identifiant défense du conjoint militaire', str) # AN
|
||||||
|
ID_SAP = ('Matricule SAP', excel_int64_converter) # A
|
||||||
|
ID_SAP_CONJOINT = ('Identifiant SAP du conjoint militaire', excel_int64_converter) # AM
|
||||||
|
INTERRUPTION_SERVICE = ('Interruption de service', str) # AG
|
||||||
|
MARQUEUR_PN = ('Marquant Personnel Navigant') # EH
|
||||||
|
NF = ('Niveau fonctionnel EIP act', str) # K
|
||||||
|
NF_POSTE = ('Niveau fonctionnel emploi occupé act C', str) # EF
|
||||||
|
NOM = ('NOM', str) # C
|
||||||
|
NOMBRE_ENFANTS = ("Nbr total d'enfants", excel_uint64_converter) # O
|
||||||
|
NR_OU_IRIS = ('IRIS / RAC retenu A', excel_int64_converter) # CN
|
||||||
|
NR_OU_IRIS_1 = ('IRIS / RAC retenu A-1', excel_int64_converter) # CT
|
||||||
|
NR_OU_IRIS_2 = ('IRIS / RAC retenu A-2', excel_int64_converter) # CZ
|
||||||
|
NR_OU_IRIS_3 = ('IRIS / RAC retenu A-3', excel_int64_converter) # DF
|
||||||
|
NR_OU_IRIS_4 = ('IRIS / RAC retenu A-4', excel_int64_converter) # DL
|
||||||
|
NR_OU_IRIS_5 = ('IRIS / RAC retenu A-5', excel_int64_converter) # DR
|
||||||
|
ORIGINE_RECRUTEMENT = ('Origine recrutement LA', str) # Q
|
||||||
|
PLS_GB_MAX = ('PLS GB Max') # EI
|
||||||
|
POSITION_STATUTAIRE = ('Position statutaire act L', str) # AE
|
||||||
|
POTENTIEL_RESPONSABILITE_SUP = ('Potentiel responsabilités catégorie sup A', str) # CQ
|
||||||
|
POTENTIEL_RESPONSABILITE_SUP_1 = ('Potentiel responsabilités catégorie sup A-1', str) # CW
|
||||||
|
POTENTIEL_RESPONSABILITE_SUP_2 = ('Potentiel responsabilités catégorie sup A-2', str) # DC
|
||||||
|
POTENTIEL_RESPONSABILITE_SUP_3 = ('Potentiel responsabilités catégorie sup A-3', str) # DI
|
||||||
|
POTENTIEL_RESPONSABILITE_SUP_4 = ('Potentiel responsabilités catégorie sup A-4', str) # DO
|
||||||
|
POTENTIEL_RESPONSABILITE_SUP_5 = ('Potentiel responsabilités catégorie sup A-5', str) # DU
|
||||||
|
PRENOM = ('Prénom', str) # D
|
||||||
|
PROFESSION_CONJOINT = ('Profession conjoint Act L', str) # AL
|
||||||
|
RAC_OU_IRIS_CUMULE = ('NR/NGC cumulé A', excel_int64_converter) # CM
|
||||||
|
RAC_OU_IRIS_CUMULE_1 = ('NR/NGC cumulé A-1', excel_int64_converter) # CS
|
||||||
|
RAC_OU_IRIS_CUMULE_2 = ('NR/NGC cumulé A-2', excel_int64_converter) # CY
|
||||||
|
RAC_OU_IRIS_CUMULE_3 = ('NR/NGC cumulé A-3', excel_int64_converter) # DE
|
||||||
|
RAC_OU_IRIS_CUMULE_4 = ('NR/NGC cumulé A-4', excel_int64_converter) # DK
|
||||||
|
RAC_OU_IRIS_CUMULE_5 = ('NR/NGC cumulé A-5', excel_int64_converter) # DQ
|
||||||
|
REGROUPEMENT_ORIGINE_RECRUTEMENT = ('Regroupement origine recrutement C', str) # M
|
||||||
|
RF_QSR = ('QSR A', str) # CO
|
||||||
|
RF_QSR_1 = ('QSR A-1', str) # CU
|
||||||
|
RF_QSR_2 = ('QSR A-2', str) # DA
|
||||||
|
RF_QSR_3 = ('QSR A-3', str) # DG
|
||||||
|
RF_QSR_4 = ('QSR A-4', str) # DM
|
||||||
|
RF_QSR_5 = ('QSR A-5', str) # DS
|
||||||
|
SEXE = ('Sexe', str) # CJ
|
||||||
|
SEXE_CONJOINT = ('Sexe du conjoint', str) # AT
|
||||||
|
SITUATION_FAMILIALE = ('Situation familiale (IT0002) L', str) # CI
|
||||||
|
STATUT_CONCERTO = ('Situation admi actuelle', str) # DY
|
||||||
|
STATUT_CONCERTO_FUTUR = ('Position statutaire future', str) # EA
|
||||||
|
|
||||||
|
|
||||||
|
class FmobCols(FileCols):
|
||||||
|
""" Colonnes du fichier FMOB """
|
||||||
|
|
||||||
|
ID_SAP = ('Matricule SAP', excel_int64_converter) # A
|
||||||
|
|
||||||
|
|
||||||
|
class InseeCols(FileCols):
|
||||||
|
""" Colonnes du fichier INSEE """
|
||||||
|
|
||||||
|
CODE_INSEE = ('CODE INSEE', str) # D
|
||||||
|
CODE_POSTAL = ('CODE POSTAL', str) # C
|
||||||
|
CREDO_FE = ('CREDO FE act', str) # B
|
||||||
|
ID_SAP = ('Matricule SAP', excel_int64_converter) # A
|
||||||
|
|
||||||
|
|
||||||
|
class ReoCols(FileCols):
|
||||||
|
""" Colonnes du fichier REO """
|
||||||
|
|
||||||
|
ANNEE_PROJET = ('Année Projet', str) # B
|
||||||
|
CATEGORIE = ('Catégorie /EFF CELL', str) # P
|
||||||
|
CODE_NF = ('NFEO', str) # W
|
||||||
|
CODE_POSTAL = ('Code Postal /OB G', str) # J
|
||||||
|
DOMAINE = ('DEO', str) # U
|
||||||
|
DOMAINE_GESTION = ('Postes NFS', str) # AA
|
||||||
|
EIP = ('MGS gestionnaire / EFF CELL', str) # T
|
||||||
|
FONCTION_ID = ('ETR Code / EFF CELL', str) # Y
|
||||||
|
FONCTION_LIBELLE = ('ETR Libellé / EFF CELL', str) # Z
|
||||||
|
FORMATION_EMPLOI = ('Code CREDO Long OB', str) # F
|
||||||
|
FILIERE = ('FEO', str) # V
|
||||||
|
ID_POSTE = ('N° De Poste', str) # X
|
||||||
|
|
||||||
|
|
||||||
17
backend-django/backend/utils/alimentation_decorators.py
Normal file
17
backend-django/backend/utils/alimentation_decorators.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from logging import Logger, LoggerAdapter
|
||||||
|
from typing import Any, List, Set, Tuple, Union
|
||||||
|
|
||||||
|
from .logging import TAG_DATA_FEED, TAG_PERF, get_logger
|
||||||
|
|
||||||
|
|
||||||
|
def get_data_logger(value: Any = None, tags: Union[str, List[str], Set[str], Tuple[str]] = None) -> Union[Logger, LoggerAdapter]:
|
||||||
|
""" variante spécifique de 'get_logger' qui ajoute toujours le tag TAG_DATA_FEED """
|
||||||
|
all_tags = TAG_DATA_FEED
|
||||||
|
if tags:
|
||||||
|
all_tags = [TAG_DATA_FEED, *tags] if isinstance(tags, (list, set, tuple)) else [TAG_DATA_FEED, tags]
|
||||||
|
return get_logger(value, all_tags)
|
||||||
|
|
||||||
|
|
||||||
|
def data_perf_logger_factory(func):
|
||||||
|
""" fonction de création de logger pour la performance et l'alimentation, à utiliser avec 'execution_time' ou 'query_count' """
|
||||||
|
return get_data_logger(func, TAG_PERF)
|
||||||
32
backend-django/backend/utils/attributes.py
Normal file
32
backend-django/backend/utils/attributes.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
|
|
||||||
|
def rgetattr(obj, attr: str, safe: bool = False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Version récursive de getattr pour utiliser une propriété chaînée même avec None au milieu.
|
||||||
|
exemples :
|
||||||
|
- rgetattr(obj, 'a.b.c')
|
||||||
|
- rgetattr(obj, 'a.b.c', safe=True)
|
||||||
|
|
||||||
|
:param obj: cible
|
||||||
|
:type obj: Any
|
||||||
|
|
||||||
|
:param attr: propriété qui peut être chaînée en séparant par '.'
|
||||||
|
:type attr: str
|
||||||
|
|
||||||
|
:param safe: indique qu'il faut tester la présence de l'attribut avec hasattr (utile pour les relations entre modèles Django), defaults to False
|
||||||
|
:type safe: bool
|
||||||
|
|
||||||
|
:return: valeur de l'attribut
|
||||||
|
:rtype: Any
|
||||||
|
"""
|
||||||
|
def _getattr(obj, attr):
|
||||||
|
return getattr(obj, attr, *args) if not safe or hasattr(obj, attr) else None
|
||||||
|
return functools.reduce(_getattr, [obj] + attr.split('.'))
|
||||||
|
|
||||||
|
def safe_rgetattr(obj, attr: str, safe: bool = False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
version safe de rgetattr
|
||||||
|
exemples : safe_rgetattr(obj, 'a.b.c')
|
||||||
|
"""
|
||||||
|
return rgetattr(obj, attr, safe=True, *args, **kwargs)
|
||||||
266
backend-django/backend/utils/decisions.py
Normal file
266
backend-django/backend/utils/decisions.py
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
|
from ..models import Administre, CustomUser, Decision, DecisionChoices, Administres_Pams
|
||||||
|
from ..models import StatutPamChoices as StatutPam
|
||||||
|
from .logging import get_logger
|
||||||
|
from .permissions import KEY_WRITE, Profiles, get_profiles_by_adm
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
# clé d'une valeur de l'arbre de décision : choix disponibles
|
||||||
|
KEY_CHOICES = 'choices'
|
||||||
|
|
||||||
|
# clé d'une valeur de l'arbre de décision : profils habilités à modifier le statut
|
||||||
|
KEY_PROFILES = 'editable_by'
|
||||||
|
|
||||||
|
# clé d'un élément de "get_available_decisions" : pour une création
|
||||||
|
KEY_CREATE = 'creation'
|
||||||
|
|
||||||
|
# clé d'un élément de "get_available_decisions" : pour une mise à jour
|
||||||
|
KEY_UPDATE = 'update'
|
||||||
|
|
||||||
|
class ExtraDecisions(str, Enum):
|
||||||
|
""" décisions en plus """
|
||||||
|
|
||||||
|
EMPTY = ''
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
|
def __init_decisions():
|
||||||
|
D = DecisionChoices
|
||||||
|
EX = ExtraDecisions
|
||||||
|
P = Profiles
|
||||||
|
return {
|
||||||
|
# === commun ===
|
||||||
|
|
||||||
|
None: {
|
||||||
|
KEY_PROFILES: (P.FILIERE, P.BVT),
|
||||||
|
KEY_CHOICES: (D.PROPOSITION_FE, D.HME_PROPOSITION_VIVIER)
|
||||||
|
},
|
||||||
|
D.REMIS_A_DISPOSITION: {
|
||||||
|
KEY_PROFILES: (P.FILIERE,),
|
||||||
|
KEY_CHOICES: (EX.EMPTY,),
|
||||||
|
},
|
||||||
|
|
||||||
|
# === en métropole ===
|
||||||
|
|
||||||
|
D.PROPOSITION_FE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL),
|
||||||
|
KEY_CHOICES: (D.DIALOGUE_EN_COURS, D.FOREMP_EN_COURS)
|
||||||
|
},
|
||||||
|
D.DIALOGUE_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||||
|
KEY_CHOICES: (D.DIALOGUE_TERMINE, D.DIALOGUE_INFRUCTUEUX)
|
||||||
|
},
|
||||||
|
D.DIALOGUE_TERMINE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||||
|
KEY_CHOICES: (D.FOREMP_EN_COURS,)
|
||||||
|
},
|
||||||
|
D.DIALOGUE_INFRUCTUEUX: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||||
|
KEY_CHOICES: (D.FOREMP_EN_COURS, D.REMIS_A_DISPOSITION)
|
||||||
|
},
|
||||||
|
D.FOREMP_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||||
|
KEY_CHOICES: (D.FOREMP_TERMINE,)
|
||||||
|
},
|
||||||
|
D.FOREMP_TERMINE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL,),
|
||||||
|
KEY_CHOICES: (D.PREPOSITIONNE, D.REMIS_A_DISPOSITION)
|
||||||
|
},
|
||||||
|
D.PREPOSITIONNE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.POSITIONNE, D.REMIS_A_DISPOSITION)
|
||||||
|
},
|
||||||
|
D.POSITIONNE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.OMIP_EN_COURS, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION)
|
||||||
|
},
|
||||||
|
D.OMIP_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.OMIP_TERMINE, D.REMIS_A_DISPOSITION),
|
||||||
|
},
|
||||||
|
D.OMIP_TERMINE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.ATTENTE_AVIONAGE, D.OMI_EN_COURS, D.REMIS_A_DISPOSITION),
|
||||||
|
},
|
||||||
|
D.ATTENTE_AVIONAGE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.OMI_EN_COURS,),
|
||||||
|
},
|
||||||
|
D.OMI_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.OMI_ACTIVE, D.REMIS_A_DISPOSITION),
|
||||||
|
},
|
||||||
|
D.OMI_ACTIVE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.OMI_ANNULE,),
|
||||||
|
},
|
||||||
|
D.OMI_ANNULE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.REMIS_A_DISPOSITION,),
|
||||||
|
},
|
||||||
|
|
||||||
|
# === hors métropole (HME) ===
|
||||||
|
|
||||||
|
D.HME_PROPOSITION_VIVIER: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_DIALOGUE_INITIE,)
|
||||||
|
},
|
||||||
|
D.HME_ETUDE_DESISTEMENT: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_DESISTEMENT,)
|
||||||
|
},
|
||||||
|
D.HME_DESISTEMENT: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.REMIS_A_DISPOSITION,)
|
||||||
|
},
|
||||||
|
D.HME_DIALOGUE_INITIE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_DIALOGUE_EN_COURS,)
|
||||||
|
},
|
||||||
|
D.HME_DIALOGUE_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_DIALOGUE_TERMINE, D.HME_DIALOGUE_INFRUCTUEUX)
|
||||||
|
},
|
||||||
|
D.HME_DIALOGUE_TERMINE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_PREPOSITIONNE,)
|
||||||
|
},
|
||||||
|
D.HME_DIALOGUE_INFRUCTUEUX: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_ETUDE_DESISTEMENT, D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION)
|
||||||
|
},
|
||||||
|
D.HME_FOREMP_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_FOREMP_TERMINE, D.HME_OMI_EN_COURS)
|
||||||
|
},
|
||||||
|
D.HME_FOREMP_TERMINE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS)
|
||||||
|
},
|
||||||
|
D.HME_PREPOSITIONNE: {
|
||||||
|
KEY_PROFILES: (P.HME,),
|
||||||
|
KEY_CHOICES: (D.HME_VALIDATION_EXPERT, D.HME_REFUS_EXPERT)
|
||||||
|
},
|
||||||
|
D.HME_VALIDATION_EXPERT: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_POSITIONNE,)
|
||||||
|
},
|
||||||
|
D.HME_POSITIONNE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_OMIP_EN_COURS, D.HME_OMI_EN_COURS, D.HME_FOREMP_EN_COURS)
|
||||||
|
},
|
||||||
|
D.HME_REFUS_EXPERT: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_PREPOSITIONNE, D.REMIS_A_DISPOSITION),
|
||||||
|
},
|
||||||
|
D.HME_OMIP_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_OMIP_TERMINE,),
|
||||||
|
},
|
||||||
|
D.HME_OMIP_TERMINE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_ATTENTE_AVIONAGE, D.HME_OMI_EN_COURS),
|
||||||
|
},
|
||||||
|
D.HME_ATTENTE_AVIONAGE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_OMI_EN_COURS,),
|
||||||
|
},
|
||||||
|
D.HME_OMI_EN_COURS: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_ACTUEL, P.PCP_FUTUR),
|
||||||
|
KEY_CHOICES: (D.HME_OMI_ACTIVE,),
|
||||||
|
},
|
||||||
|
D.HME_OMI_ACTIVE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.HME_OMI_ANNULE,),
|
||||||
|
},
|
||||||
|
D.HME_OMI_ANNULE: {
|
||||||
|
KEY_PROFILES: (P.PCP, P.PCP_FUTUR,),
|
||||||
|
KEY_CHOICES: (D.REMIS_A_DISPOSITION,),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# enchaînement des décisions
|
||||||
|
DECISIONS = __init_decisions()
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_decisions() -> Tuple[DecisionChoices]:
|
||||||
|
"""
|
||||||
|
Renvoie tous les statuts de décisions possibles. Une décision vide correspondrait à une absence de décision et n'est donc pas présente.
|
||||||
|
|
||||||
|
:return: toutes les décisions
|
||||||
|
:rtype: Tuple[DecisionChoices]
|
||||||
|
"""
|
||||||
|
return tuple(DecisionChoices)
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_decisions(
|
||||||
|
administres: Union[List[Administres_Pams], Tuple[Administres_Pams]],
|
||||||
|
user: CustomUser = None,
|
||||||
|
profiles_by_adm: Dict[int, Tuple[Profiles]] = None
|
||||||
|
) -> Dict[int, Dict[str, Tuple[Union[DecisionChoices, ExtraDecisions]]]]:
|
||||||
|
"""
|
||||||
|
Renvoie les décisions disponibles pour l'utilisateur s'il modifie le statut de décision des administrés donnés.
|
||||||
|
|
||||||
|
:param administres: Administres_Pams
|
||||||
|
:type administres: Union[List[Administres_Pams], Tuple[Administres_Pams]]
|
||||||
|
|
||||||
|
:param user: utilisateur
|
||||||
|
:type user: class:`CustomUser`, optional
|
||||||
|
|
||||||
|
:param profiles_by_adm: profils pour chaque administré (voir get_profiles_by_adm)
|
||||||
|
:type profiles_by_adm: Dict[int, Tuple[Profiles]], optional
|
||||||
|
|
||||||
|
:return: dictionnaire de dictionnaires {<ID SAP>: {<KEY_CREATE>: <décisions>, <KEY_UPDATE>: <décisions>} }
|
||||||
|
:rtype: Dict[int, Dict[str, Tuple[Union[DecisionChoices, ExtraDecisions]]]]
|
||||||
|
"""
|
||||||
|
pam_without_decision = tuple(x for x in StatutPam if not x.dec_enabled)
|
||||||
|
pam_with_decision = tuple(x for x in StatutPam if x.dec_enabled)
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
# restrictions : dictionnaire de dictionnaires {<ID SAP>: <même forme qu'une valeur de DECISIONS>}
|
||||||
|
restrictions_create_by_adm = {}
|
||||||
|
restrictions_update_by_adm = {}
|
||||||
|
|
||||||
|
adm_to_process = []
|
||||||
|
for adm in administres:
|
||||||
|
adm_id = adm.pk
|
||||||
|
pam = adm.a_statut_pam_annee
|
||||||
|
if pam in pam_without_decision:
|
||||||
|
# le statut PAM ne permet aucun choix
|
||||||
|
result[adm_id] = {KEY_CREATE: (), KEY_UPDATE: ()}
|
||||||
|
elif pam in pam_with_decision:
|
||||||
|
# le statut PAM active l'arbre de décisions
|
||||||
|
adm_to_process.append(adm)
|
||||||
|
statut_decision = adm.decision.de_decision or None if hasattr(adm, Administres_Pams.Cols.REL_DECISION) else None
|
||||||
|
restrictions_update_by_adm[adm_id] = DECISIONS.get(statut_decision) or {}
|
||||||
|
else:
|
||||||
|
logger.info('statut PAM non géré pour les décisions possibles : %s', pam)
|
||||||
|
result[adm_id] = {KEY_CREATE: (), KEY_UPDATE: ()}
|
||||||
|
|
||||||
|
if adm_to_process:
|
||||||
|
default_restrictions_create = DECISIONS.get(None) or {}
|
||||||
|
|
||||||
|
def get_decisions(profiles, restrictions) -> Tuple[Union[DecisionChoices, ExtraDecisions]]:
|
||||||
|
allowed_profiles = restrictions.get(KEY_PROFILES) or ()
|
||||||
|
choices = restrictions.get(KEY_CHOICES)
|
||||||
|
decisions = ()
|
||||||
|
if choices and any(p in allowed_profiles for p in profiles):
|
||||||
|
decisions = tuple(choices)
|
||||||
|
return decisions
|
||||||
|
|
||||||
|
_profiles_by_adm = profiles_by_adm or get_profiles_by_adm(user, *adm_to_process)
|
||||||
|
for adm in adm_to_process:
|
||||||
|
adm_id = adm.pk
|
||||||
|
profiles = (_profiles_by_adm.get(adm_id) or {}).get(KEY_WRITE) or ()
|
||||||
|
result.setdefault(adm_id, {
|
||||||
|
KEY_CREATE: get_decisions(profiles, restrictions_create_by_adm.get(adm_id) or default_restrictions_create),
|
||||||
|
KEY_UPDATE: get_decisions(profiles, restrictions_update_by_adm.get(adm_id) or {})
|
||||||
|
})
|
||||||
|
return result
|
||||||
113
backend-django/backend/utils/decorator.py
Normal file
113
backend-django/backend/utils/decorator.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
from django.db import connections
|
||||||
|
from enum import Enum
|
||||||
|
from typing import List, Optional, Set, Tuple, Union
|
||||||
|
import logging
|
||||||
|
import functools
|
||||||
|
import inspect
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class InfoAppel(Enum):
|
||||||
|
""" infos supplémentaires possibles """
|
||||||
|
|
||||||
|
# ajouté au nom de logger
|
||||||
|
CLASSE = 'classe'
|
||||||
|
# ajouté dans le message
|
||||||
|
PID = 'pid'
|
||||||
|
|
||||||
|
|
||||||
|
def get_nom_module(func) -> str:
|
||||||
|
mod = inspect.getmodule(func)
|
||||||
|
return mod.__name__ if mod else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_nombre_requetes() -> int:
|
||||||
|
""" renvoie le nombre total de requêtes (il peut y avoir plusieurs connexions) """
|
||||||
|
|
||||||
|
return sum(len(c.queries) for c in connections.all())
|
||||||
|
|
||||||
|
|
||||||
|
def get_nom_classe(inclure: Union[ List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel] ] = (), *args) -> str:
|
||||||
|
""" renvoie le nom de classe correspondant à l'appel de fonction """
|
||||||
|
|
||||||
|
nom_classe = None
|
||||||
|
if InfoAppel.CLASSE in inclure and args:
|
||||||
|
classe = getattr(args[0], '__class__', None)
|
||||||
|
nom_classe = getattr(classe, '__name__') if classe else None
|
||||||
|
return nom_classe
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(nom_module: Optional[str], nom_classe: Optional[str]) -> logging.Logger:
|
||||||
|
""" renvoie le logger correspondant au module et à la classe """
|
||||||
|
|
||||||
|
return logging.getLogger(f'{nom_module}.{nom_classe}' if nom_module and nom_classe else nom_classe or nom_module)
|
||||||
|
|
||||||
|
|
||||||
|
def duree_execution(avertir_apres: int = None, inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]] = ()):
|
||||||
|
"""
|
||||||
|
décorateur pour tracer le temps d'exécution d'une fonction
|
||||||
|
|
||||||
|
:param avertir_apres: durée (en ms) au-delà de laquelle on souhaite un log d'avertissement, defaults to None
|
||||||
|
:type avertir_apres: int, optional
|
||||||
|
|
||||||
|
:param inclure: infos supplémentaires, defaults to ()
|
||||||
|
:type inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]], optional
|
||||||
|
|
||||||
|
:return: résultat de la fonction d'origine
|
||||||
|
"""
|
||||||
|
def inner(func):
|
||||||
|
nom_module = get_nom_module(func)
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
temps_debut = time.time()
|
||||||
|
try:
|
||||||
|
resultat = func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
temps_ecoule = round((time.time() - temps_debut) * 1000)
|
||||||
|
|
||||||
|
func_logger = get_logger(nom_module, get_nom_classe(inclure, *args))
|
||||||
|
log_level = logging.WARNING if isinstance(avertir_apres, int) and temps_ecoule > avertir_apres else logging.DEBUG
|
||||||
|
func_logger.log(log_level, "%s'%s' exécuté en %d ms", '[%s] ' % os.getpid() if InfoAppel.PID in inclure else '', func.__name__, temps_ecoule)
|
||||||
|
except Exception:
|
||||||
|
logger.exception('impossible de tracer la durée')
|
||||||
|
return resultat
|
||||||
|
return wrapper
|
||||||
|
return inner
|
||||||
|
|
||||||
|
def nombre_requetes(avertir_apres: int = None, inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]] = ()):
|
||||||
|
"""
|
||||||
|
décorateur pour tracer le nombre de requêtes d'une fonction
|
||||||
|
|
||||||
|
:param avertir_apres: nombre de requêtes au-delà duquel on souhaite un log d'avertissement, defaults to None
|
||||||
|
:type avertir_apres: int, optional
|
||||||
|
|
||||||
|
:param inclure: infos supplémentaires, defaults to ()
|
||||||
|
:type inclure: Union[List[InfoAppel], Set[InfoAppel], Tuple[InfoAppel]], optional
|
||||||
|
|
||||||
|
:return: résultat de la fonction d'origine
|
||||||
|
"""
|
||||||
|
def inner(func):
|
||||||
|
nom_module = get_nom_module(func)
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
nb_debut = get_nombre_requetes()
|
||||||
|
try:
|
||||||
|
resultat = func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
nb_requetes = get_nombre_requetes() - nb_debut
|
||||||
|
|
||||||
|
func_logger = get_logger(nom_module, get_nom_classe(inclure, *args))
|
||||||
|
log_level = logging.WARNING if isinstance(avertir_apres, int) and nb_requetes > avertir_apres else logging.DEBUG
|
||||||
|
func_logger.log(log_level, "%s'%s' a exécuté %d requête(s)", '[%s] ' % os.getpid() if InfoAppel.PID in inclure else '', func.__name__, nb_requetes)
|
||||||
|
except Exception:
|
||||||
|
logger.exception('impossible de tracer le nombre de requêtes')
|
||||||
|
return resultat
|
||||||
|
return wrapper
|
||||||
|
return inner
|
||||||
294
backend-django/backend/utils/decorators.py
Normal file
294
backend-django/backend/utils/decorators.py
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
import functools
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from enum import Enum, auto
|
||||||
|
from logging import Logger, LoggerAdapter
|
||||||
|
from types import (BuiltinFunctionType, BuiltinMethodType,
|
||||||
|
ClassMethodDescriptorType, FunctionType,
|
||||||
|
MethodDescriptorType, MethodType)
|
||||||
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
|
from django.db import connections
|
||||||
|
|
||||||
|
from .logging import TAG_DATA_FEED, TAG_PERF, get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# attribut de classe : logger
|
||||||
|
CLASS_ATTR_LOGGER = 'logger'
|
||||||
|
|
||||||
|
# clé du dictionnaire contexte : désactivé
|
||||||
|
CTX_KEY_DISABLED = 'disabled'
|
||||||
|
|
||||||
|
# clé du dictionnaire contexte : logger
|
||||||
|
CTX_KEY_LOGGER = 'logger'
|
||||||
|
|
||||||
|
# attribut de décorateur : contexte
|
||||||
|
DECORATOR_ATTR_CTX = 'decorator_ctx'
|
||||||
|
|
||||||
|
# attribut de décorateur : clé
|
||||||
|
DECORATOR_ATTR_KEY = 'decorator_key'
|
||||||
|
|
||||||
|
# attribut de décorateur : cible (classe, fonction)
|
||||||
|
DECORATOR_ATTR_TARGET = 'decorator_target'
|
||||||
|
|
||||||
|
# types de fonctions
|
||||||
|
FUNCTION_TYPES = (
|
||||||
|
BuiltinFunctionType,
|
||||||
|
BuiltinMethodType,
|
||||||
|
ClassMethodDescriptorType,
|
||||||
|
FunctionType,
|
||||||
|
MethodDescriptorType,
|
||||||
|
MethodType,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_query_count() -> int:
|
||||||
|
""" renvoie le nombre total de requêtes (il peut y avoir plusieurs connexions) """
|
||||||
|
|
||||||
|
return sum(len(c.queries) for c in connections.all())
|
||||||
|
|
||||||
|
|
||||||
|
class OnConflict(Enum):
|
||||||
|
""" choix de gestion de conflit quand le même décorateur est déjà utilisé """
|
||||||
|
|
||||||
|
# laisse l'ancien décorateur
|
||||||
|
SKIP = auto()
|
||||||
|
|
||||||
|
# remplace l'ancien décorateur
|
||||||
|
REPLACE = auto()
|
||||||
|
|
||||||
|
# ajoute à l'ancien décorateur
|
||||||
|
STACK = auto()
|
||||||
|
|
||||||
|
|
||||||
|
# amélioration possible : et s'il y a plusieurs décorateurs de même clé ?
|
||||||
|
def _find_former_decorator(key: str, target: Callable) -> Tuple[Optional[Callable], Optional[Callable]]:
|
||||||
|
"""
|
||||||
|
Trouve le décorateur de même clé si la cible est déjà décorée. Un décorateur désactivé est ignoré.
|
||||||
|
note : ne fonctionne pas correctement s'il existe un décorateur intermédiaire sans attribut DECORATOR_ATTR_TARGET
|
||||||
|
|
||||||
|
:param key: clé de décorateur
|
||||||
|
:type key: str
|
||||||
|
|
||||||
|
:param target: fonction potentiellement décorée
|
||||||
|
:type target: Callable
|
||||||
|
|
||||||
|
:return: ancien décorateur + ancienne cible
|
||||||
|
:rtype: Tuple[Callable, Callable]
|
||||||
|
"""
|
||||||
|
current = target
|
||||||
|
decorator_key = getattr(current, DECORATOR_ATTR_KEY, None)
|
||||||
|
decorator_target = getattr(current, DECORATOR_ATTR_TARGET, None)
|
||||||
|
|
||||||
|
while decorator_key and decorator_key != key and decorator_target:
|
||||||
|
current = decorator_target
|
||||||
|
decorator_key = getattr(current, DECORATOR_ATTR_KEY, None)
|
||||||
|
decorator_target = getattr(current, DECORATOR_ATTR_TARGET, None)
|
||||||
|
|
||||||
|
# les décorateurs désactivés ne comptent pas
|
||||||
|
if decorator_key == key and not getattr(current, DECORATOR_ATTR_CTX, {}).get(CTX_KEY_DISABLED, False):
|
||||||
|
return current, decorator_target
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def _create_decorator_from_action(
|
||||||
|
action: Callable,
|
||||||
|
decorator_key: str,
|
||||||
|
on_conflict: OnConflict = OnConflict.SKIP):
|
||||||
|
"""
|
||||||
|
Fonction d'ordre supérieur qui construit un décorateur de fonction/méthode à partir des paramètres. Ce décorateur exécute l'action fournie.
|
||||||
|
|
||||||
|
Un contexte (dictionnaire) est passé à l'appel. Il contient :
|
||||||
|
- l'état désactivé : clé CTX_KEY_DISABLED optionnelle
|
||||||
|
|
||||||
|
:param action: action à exécuter
|
||||||
|
:type action: Callable[[dict, Callable[P, R], P.args, P.kwargs], R]
|
||||||
|
|
||||||
|
:param decorator_key: clé correspondant au décorateur, permet de gérer les conflits
|
||||||
|
:type decorator_key: str, optional
|
||||||
|
|
||||||
|
:param on_conflict: stratégie de gestion de conflit quand le décorateur est déjà utilisé, defaults to OnConflict.SKIP
|
||||||
|
:type on_conflict: class:`OnConflict`
|
||||||
|
"""
|
||||||
|
def inner(func):
|
||||||
|
if on_conflict is not OnConflict.STACK:
|
||||||
|
former_decorator, former_target = _find_former_decorator(decorator_key, func)
|
||||||
|
if former_decorator:
|
||||||
|
if on_conflict is OnConflict.SKIP:
|
||||||
|
# pas de nouveau décorateur
|
||||||
|
return func
|
||||||
|
|
||||||
|
if on_conflict is OnConflict.REPLACE:
|
||||||
|
if former_decorator is func and former_target:
|
||||||
|
# ancien décorateur déjà fourni : on le remplace vraiment
|
||||||
|
func = former_target
|
||||||
|
else:
|
||||||
|
# ancien décorateur déjà décoré : on le désactive
|
||||||
|
former_ctx = getattr(former_decorator, DECORATOR_ATTR_CTX, None)
|
||||||
|
if former_ctx is None:
|
||||||
|
former_ctx = {}
|
||||||
|
getattr(former_decorator, DECORATOR_ATTR_CTX, former_ctx)
|
||||||
|
former_ctx.update({CTX_KEY_DISABLED: True})
|
||||||
|
ctx = {}
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if ctx.get(CTX_KEY_DISABLED, False):
|
||||||
|
# désactivé : simple appel de la fonction d'origine
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return action(ctx, func, *args, **kwargs)
|
||||||
|
|
||||||
|
setattr(wrapper, DECORATOR_ATTR_CTX, ctx)
|
||||||
|
setattr(wrapper, DECORATOR_ATTR_KEY, decorator_key)
|
||||||
|
setattr(wrapper, DECORATOR_ATTR_TARGET, func)
|
||||||
|
return wrapper
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
def execution_time(
|
||||||
|
level: int = logging.DEBUG,
|
||||||
|
logger_name: Any = None,
|
||||||
|
logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]] = None,
|
||||||
|
on_conflict: OnConflict = OnConflict.SKIP,
|
||||||
|
warn_after: int = None):
|
||||||
|
"""
|
||||||
|
Décorateur de fonction pour tracer le temps d'exécution (au niveau <level> avec un logger de performances).
|
||||||
|
Pour une classe, le décorateur s'applique aux fonctions/méthodes qui passent le filtre.
|
||||||
|
|
||||||
|
:param level: niveau de base du logger, defaults to logging.DEBUG
|
||||||
|
:type level: int
|
||||||
|
|
||||||
|
:param logger_name: valeur pour déterminer le nom du logger, ignorée si 'logger_factory' est renseigné, defaults to None
|
||||||
|
:type logger_name: str, optional
|
||||||
|
|
||||||
|
:param logger_factory: fonction de création du logger à partir de la fonction annotée, annule 'logger_name', defaults to None.
|
||||||
|
:type logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]], optional
|
||||||
|
|
||||||
|
:param on_conflict: stratégie de gestion de conflit quand le décorateur est déjà utilisé, defaults to OnConflict.SKIP
|
||||||
|
:type on_conflict: class:`OnConflict`
|
||||||
|
|
||||||
|
:param warn_after: durée (en ms) au-delà de laquelle on souhaite un log d'avertissement, defaults to None
|
||||||
|
:type warn_after: int, optional
|
||||||
|
|
||||||
|
:return: résultat de la fonction d'origine
|
||||||
|
"""
|
||||||
|
|
||||||
|
def action(ctx: Dict, func, *args, **kwargs):
|
||||||
|
if CTX_KEY_LOGGER not in ctx:
|
||||||
|
# initialisation du logger, pas toujours possible avant le premier appel
|
||||||
|
ctx.update({CTX_KEY_LOGGER: logger_factory(func) if logger_factory else get_logger(logger_name or func, TAG_PERF)})
|
||||||
|
|
||||||
|
temps_debut = time.time()
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
temps_ecoule = round((time.time() - temps_debut) * 1000)
|
||||||
|
log_level = logging.WARNING if isinstance(warn_after, int) and temps_ecoule > warn_after else level
|
||||||
|
ctx[CTX_KEY_LOGGER].log(log_level, "'%s' exécuté en %d ms", func.__name__, temps_ecoule)
|
||||||
|
except Exception:
|
||||||
|
logger.exception('impossible de tracer la durée')
|
||||||
|
|
||||||
|
return _create_decorator_from_action(action, inspect.currentframe().f_code.co_name, on_conflict=on_conflict)
|
||||||
|
|
||||||
|
|
||||||
|
def query_count(
|
||||||
|
level: int = logging.DEBUG,
|
||||||
|
logger_name: Any = None,
|
||||||
|
logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]] = None,
|
||||||
|
on_conflict: OnConflict = OnConflict.SKIP,
|
||||||
|
warn_after: int = None):
|
||||||
|
"""
|
||||||
|
Décorateur de fonction pour tracer le nombre de requêtes (au niveau <level> avec un logger de performances).
|
||||||
|
Pour une classe, le décorateur s'applique aux fonctions/méthodes qui passent le filtre.
|
||||||
|
|
||||||
|
:param level: niveau de base du logger, defaults to logging.DEBUG
|
||||||
|
:type level: int
|
||||||
|
|
||||||
|
:param logger_name: valeur pour déterminer le nom du logger, ignorée si 'logger_factory' est renseigné, defaults to None
|
||||||
|
:type logger_name: str, optional
|
||||||
|
|
||||||
|
:param logger_factory: fonction de création du logger à partir de la fonction annotée, annule 'logger_name', defaults to None.
|
||||||
|
:type logger_factory: Callable[[Any], Union[Logger, LoggerAdapter]], optional
|
||||||
|
|
||||||
|
:param on_conflict: stratégie de gestion de conflit quand le décorateur est déjà utilisé, defaults to OnConflict.SKIP
|
||||||
|
:type on_conflict: class:`OnConflict`
|
||||||
|
|
||||||
|
:param warn_after: nombre de requêtes au-delà duquel on souhaite un log d'avertissement, defaults to None
|
||||||
|
:type warn_after: int, optional
|
||||||
|
|
||||||
|
:return: résultat de la fonction d'origine
|
||||||
|
"""
|
||||||
|
|
||||||
|
def action(ctx: Dict, func, *args, **kwargs):
|
||||||
|
if CTX_KEY_LOGGER not in ctx:
|
||||||
|
# initialisation du logger, pas toujours possible avant le premier appel
|
||||||
|
ctx.update({CTX_KEY_LOGGER: logger_factory(func) if logger_factory else get_logger(logger_name or func, TAG_PERF)})
|
||||||
|
|
||||||
|
nb_debut = _get_query_count()
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
nb_requetes = _get_query_count() - nb_debut
|
||||||
|
log_level = logging.WARNING if isinstance(warn_after, int) and nb_requetes > warn_after else level
|
||||||
|
ctx[CTX_KEY_LOGGER].log(log_level, "'%s' a exécuté %d requête(s)", func.__name__, nb_requetes)
|
||||||
|
except Exception:
|
||||||
|
logger.exception('impossible de tracer le nombre de requêtes')
|
||||||
|
|
||||||
|
return _create_decorator_from_action(action, inspect.currentframe().f_code.co_name, on_conflict=on_conflict)
|
||||||
|
|
||||||
|
|
||||||
|
def class_logger(cls):
|
||||||
|
"""
|
||||||
|
Décorateur de classe pour stocker un logger standard dans l'attribut 'logger' (aucune gestion de conflit)
|
||||||
|
"""
|
||||||
|
if not inspect.isclass(cls):
|
||||||
|
return cls
|
||||||
|
|
||||||
|
@functools.wraps(cls, updated=())
|
||||||
|
class Wrapper(cls):
|
||||||
|
decorated = 1
|
||||||
|
|
||||||
|
setattr(Wrapper, CLASS_ATTR_LOGGER, get_logger(Wrapper))
|
||||||
|
return Wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def decorate_functions(
|
||||||
|
decorator: Callable,
|
||||||
|
func_filter: Callable[[Callable], bool],
|
||||||
|
factory: bool = False):
|
||||||
|
"""
|
||||||
|
Décorateur de classe qui applique le décorateur donné aux fonctions/méthodes (attributs d'un type de FUNCTION_TYPES) qui passent le filtre.
|
||||||
|
|
||||||
|
:param decorator: décorateur à appliquer
|
||||||
|
:type decorator: Callable
|
||||||
|
|
||||||
|
:param func_filter: filtre permettant de sélectionner les fonctions/méthodes à décorer
|
||||||
|
:type func_filter: Callable[[Callable], bool]
|
||||||
|
|
||||||
|
:param factory: True indique que le décorateur est une méthode "factory" à exécuter avec la classe, defaults to False
|
||||||
|
:type factory: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorate(cls):
|
||||||
|
if not inspect.isclass(cls):
|
||||||
|
return cls
|
||||||
|
|
||||||
|
@functools.wraps(cls, updated=())
|
||||||
|
class Wrapper(cls):
|
||||||
|
decorated = 1
|
||||||
|
|
||||||
|
_decorator = decorator(Wrapper) if factory else decorator
|
||||||
|
for attr_name in dir(Wrapper):
|
||||||
|
# __new__ et __init__ sont complexes à décorer, à gérer au cas par cas
|
||||||
|
if attr_name != '__new__' and attr_name != '__init__':
|
||||||
|
value = getattr(Wrapper, attr_name)
|
||||||
|
if isinstance(value, FUNCTION_TYPES) and func_filter(value):
|
||||||
|
setattr(Wrapper, attr_name, _decorator(value))
|
||||||
|
return Wrapper
|
||||||
|
return decorate
|
||||||
1
backend-django/backend/utils/extraction/__init__.py
Normal file
1
backend-django/backend/utils/extraction/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .administre import *
|
||||||
276
backend-django/backend/utils/extraction/administre.py
Normal file
276
backend-django/backend/utils/extraction/administre.py
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from backend.models.administre import Administre
|
||||||
|
from backend.models.administre import StatutPamChoices as StatutPam
|
||||||
|
from backend.models.domaine import Domaine
|
||||||
|
from backend.models.filiere import Filiere
|
||||||
|
from backend.models.fonction import Fonction
|
||||||
|
from backend.models.formation_emploi import FormationEmploi
|
||||||
|
from backend.models.grade import Grade
|
||||||
|
from ..alimentation import BOCols
|
||||||
|
from ..alimentation_decorators import data_perf_logger_factory, get_data_logger
|
||||||
|
from ..decorators import execution_time
|
||||||
|
|
||||||
|
logger = get_data_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@execution_time(logger_factory=data_perf_logger_factory)
|
||||||
|
def to_table_administres_bo(bo: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Création de la table Administrés à partir du fichier de données BO. Sélection et renommage des champs.
|
||||||
|
|
||||||
|
:param bo: table BO
|
||||||
|
:type bo: class:`pandas.DataFrame`
|
||||||
|
|
||||||
|
:return: data frame contenant les information des administrés
|
||||||
|
:rtype: class:`pandas.DataFrame`
|
||||||
|
"""
|
||||||
|
Cols = Administre.Cols
|
||||||
|
|
||||||
|
# import des tables contenant les clés étrangères
|
||||||
|
fonctions = pd.DataFrame.from_records(Fonction.objects.all().values())
|
||||||
|
domaines = pd.DataFrame.from_records(Domaine.objects.all().values())
|
||||||
|
filieres = pd.DataFrame.from_records(Filiere.objects.all().values())
|
||||||
|
|
||||||
|
grades = pd.DataFrame.from_records(Grade.objects.all().values())
|
||||||
|
fe = pd.DataFrame.from_records(FormationEmploi.objects.all().values())
|
||||||
|
|
||||||
|
# sélection des attributs nécessaires à la table administres
|
||||||
|
col_adm = BOCols.columns(
|
||||||
|
BOCols.ID_SAP,
|
||||||
|
BOCols.CREDO_FE,
|
||||||
|
BOCols.FONCTION,
|
||||||
|
BOCols.GRADE,
|
||||||
|
BOCols.DATE_DEBUT_GRADE,
|
||||||
|
BOCols.NOM,
|
||||||
|
BOCols.PRENOM,
|
||||||
|
BOCols.SEXE,
|
||||||
|
BOCols.ID_DEF,
|
||||||
|
BOCols.EIP,
|
||||||
|
BOCols.EIS,
|
||||||
|
BOCols.DOMAINE,
|
||||||
|
BOCols.FILIERE,
|
||||||
|
BOCols.NF,
|
||||||
|
BOCols.DOMAINE_GESTION,
|
||||||
|
BOCols.DATE_ENTREE_SERVICE,
|
||||||
|
BOCols.ARME,
|
||||||
|
BOCols.REGROUPEMENT_ORIGINE_RECRUTEMENT,
|
||||||
|
BOCols.DATE_NAISSANCE,
|
||||||
|
BOCols.DIPLOME_PLUS_HAUT_NIVEAU,
|
||||||
|
BOCols.DATE_RDC,
|
||||||
|
BOCols.DATE_DERNIER_ACR,
|
||||||
|
BOCols.DERNIER_DIPLOME,
|
||||||
|
BOCols.DATE_ARRIVEE_FE,
|
||||||
|
BOCols.POSITION_STATUTAIRE,
|
||||||
|
BOCols.DATE_POSITION_STATUAIRE,
|
||||||
|
BOCols.INTERRUPTION_SERVICE,
|
||||||
|
BOCols.SITUATION_FAMILIALE,
|
||||||
|
BOCols.DATE_MARIAGE,
|
||||||
|
BOCols.NOMBRE_ENFANTS,
|
||||||
|
BOCols.ENFANTS,
|
||||||
|
BOCols.ID_DEF_CONJOINT,
|
||||||
|
BOCols.FONCTION_1,
|
||||||
|
BOCols.FONCTION_2,
|
||||||
|
BOCols.FONCTION_3,
|
||||||
|
BOCols.FONCTION_4,
|
||||||
|
BOCols.FONCTION_5,
|
||||||
|
BOCols.FONCTION_6,
|
||||||
|
BOCols.FONCTION_7,
|
||||||
|
BOCols.FONCTION_8,
|
||||||
|
BOCols.FONCTION_9,
|
||||||
|
BOCols.DATE_FONCTION_1,
|
||||||
|
BOCols.DATE_FONCTION_2,
|
||||||
|
BOCols.DATE_FONCTION_3,
|
||||||
|
BOCols.DATE_FONCTION_4,
|
||||||
|
BOCols.DATE_FONCTION_5,
|
||||||
|
BOCols.DATE_FONCTION_6,
|
||||||
|
BOCols.DATE_FONCTION_7,
|
||||||
|
BOCols.DATE_FONCTION_8,
|
||||||
|
BOCols.DATE_FONCTION_9,
|
||||||
|
BOCols.NF_POSTE,
|
||||||
|
BOCols.DOMAINE_POSTE,
|
||||||
|
BOCols.FILIERE_POSTE,
|
||||||
|
BOCols.PLS_GB_MAX,
|
||||||
|
BOCols.MARQUEUR_PN,
|
||||||
|
BOCols.PROFESSION_CONJOINT,
|
||||||
|
BOCols.ID_SAP_CONJOINT,
|
||||||
|
BOCols.SEXE_CONJOINT,
|
||||||
|
BOCols.ORIGINE_RECRUTEMENT,
|
||||||
|
BOCols.DATE_LIEN_SERVICE,
|
||||||
|
BOCols.AGE_ANNEES,
|
||||||
|
BOCols.STATUT_CONCERTO,
|
||||||
|
BOCols.DATE_STATUT_CONCERTO,
|
||||||
|
BOCols.STATUT_CONCERTO_FUTUR,
|
||||||
|
BOCols.DATE_STATUT_CONCERTO_FUTUR,
|
||||||
|
BOCols.FUD,
|
||||||
|
BOCols.DATE_FUD
|
||||||
|
)
|
||||||
|
administres = bo[col_adm]
|
||||||
|
|
||||||
|
# jointure avec les tables contenant les clés étrangères
|
||||||
|
# mapping avec les postes
|
||||||
|
administres = administres.merge(fonctions, how='left', left_on=BOCols.FONCTION.value, right_on='fon_libelle')
|
||||||
|
logger.debug("Nombre d'administrés dans le fichier")
|
||||||
|
logger.debug('total administres : %s', administres.shape[0])
|
||||||
|
administres = administres.merge(grades, how='inner', left_on=BOCols.GRADE.value, right_on='gr_code')
|
||||||
|
logger.debug("Filtrage par grade reconnu")
|
||||||
|
logger.debug("Nombre d'administres : %s", administres.shape[0])
|
||||||
|
administres = administres.merge(fe, how='inner', left_on=BOCols.CREDO_FE.value, right_on='fe_code')
|
||||||
|
logger.debug("Filtrage par FE reconnue")
|
||||||
|
logger.debug("Nombre d'administres : %s", administres.shape[0])
|
||||||
|
|
||||||
|
# sélection et renommage des champs (BIEN FAIRE LE MAPPING DES COLONNES QU'ON VEUT GARDER)
|
||||||
|
adm_mapping = BOCols.col_mapping({
|
||||||
|
BOCols.ID_SAP: Cols.PK,
|
||||||
|
'fe_code': f'{Cols.REL_FORMATION_EMPLOI}_id',
|
||||||
|
'fon_id': "a_code_fonction",
|
||||||
|
BOCols.GRADE: f'{Cols.REL_GRADE}_id',
|
||||||
|
BOCols.DATE_DEBUT_GRADE: "a_grade_date_debut",
|
||||||
|
BOCols.FONCTION: "a_fonction",
|
||||||
|
BOCols.NOM: "a_nom",
|
||||||
|
BOCols.PRENOM: "a_prenom",
|
||||||
|
BOCols.SEXE: "a_sexe",
|
||||||
|
BOCols.ID_DEF: "a_id_def",
|
||||||
|
BOCols.EIP: "a_eip",
|
||||||
|
BOCols.EIP: "a_eip_fiche_detaille",
|
||||||
|
BOCols.EIS: "a_eis",
|
||||||
|
BOCols.DOMAINE: Cols.REL_DOMAINE,
|
||||||
|
BOCols.FILIERE: Cols.REL_FILIERE,
|
||||||
|
BOCols.NF: 'a_nf',
|
||||||
|
BOCols.DOMAINE_GESTION: "a_domaine_gestion",
|
||||||
|
BOCols.DATE_ENTREE_SERVICE: "a_date_entree_service",
|
||||||
|
BOCols.ARME: "a_arme",
|
||||||
|
BOCols.REGROUPEMENT_ORIGINE_RECRUTEMENT: "a_rg_origine_recrutement",
|
||||||
|
BOCols.DATE_RDC: 'a_date_rdc',
|
||||||
|
BOCols.DATE_DERNIER_ACR: 'a_date_dernier_acr',
|
||||||
|
BOCols.DATE_NAISSANCE: "a_date_naissance",
|
||||||
|
BOCols.DIPLOME_PLUS_HAUT_NIVEAU: "a_diplome_hl",
|
||||||
|
BOCols.DERNIER_DIPLOME: "a_dernier_diplome",
|
||||||
|
BOCols.CREDO_FE: "a_credo_fe",
|
||||||
|
BOCols.DATE_ARRIVEE_FE: "a_date_arrivee_fe",
|
||||||
|
BOCols.POSITION_STATUTAIRE: "a_pos_statuaire",
|
||||||
|
BOCols.DATE_POSITION_STATUAIRE: "a_date_pos_statuaire",
|
||||||
|
BOCols.INTERRUPTION_SERVICE: "a_interruption_service",
|
||||||
|
BOCols.SITUATION_FAMILIALE: "a_situation_fam",
|
||||||
|
BOCols.DATE_MARIAGE: "a_date_mariage",
|
||||||
|
BOCols.NOMBRE_ENFANTS: "a_nombre_enfants",
|
||||||
|
BOCols.ENFANTS: "a_enfants",
|
||||||
|
BOCols.ID_SAP_CONJOINT: "a_sap_conjoint",
|
||||||
|
BOCols.FONCTION_1: "a_fonction1",
|
||||||
|
BOCols.FONCTION_2: "a_fonction2",
|
||||||
|
BOCols.FONCTION_3: "a_fonction3",
|
||||||
|
BOCols.FONCTION_4: "a_fonction4",
|
||||||
|
BOCols.FONCTION_5: "a_fonction5",
|
||||||
|
BOCols.FONCTION_6: "a_fonction6",
|
||||||
|
BOCols.FONCTION_7: "a_fonction7",
|
||||||
|
BOCols.FONCTION_8: "a_fonction8",
|
||||||
|
BOCols.FONCTION_9: "a_fonction9",
|
||||||
|
BOCols.DATE_FONCTION_1: "a_date_fonction1",
|
||||||
|
BOCols.DATE_FONCTION_2: "a_date_fonction2",
|
||||||
|
BOCols.DATE_FONCTION_3: "a_date_fonction3",
|
||||||
|
BOCols.DATE_FONCTION_4: "a_date_fonction4",
|
||||||
|
BOCols.DATE_FONCTION_5: "a_date_fonction5",
|
||||||
|
BOCols.DATE_FONCTION_6: "a_date_fonction6",
|
||||||
|
BOCols.DATE_FONCTION_7: "a_date_fonction7",
|
||||||
|
BOCols.DATE_FONCTION_8: "a_date_fonction8",
|
||||||
|
BOCols.DATE_FONCTION_9: "a_date_fonction9",
|
||||||
|
BOCols.NF_POSTE: "a_nf_poste",
|
||||||
|
BOCols.DOMAINE_POSTE: "a_domaine_poste",
|
||||||
|
BOCols.FILIERE_POSTE: "a_filiere_poste",
|
||||||
|
BOCols.PLS_GB_MAX: "a_pls_gb_max",
|
||||||
|
BOCols.MARQUEUR_PN: "a_marqueur_pn",
|
||||||
|
BOCols.PROFESSION_CONJOINT: "a_profession_conjoint",
|
||||||
|
BOCols.ID_DEF_CONJOINT: "a_id_def_conjoint",
|
||||||
|
BOCols.SEXE_CONJOINT: "a_sexe_conjoint",
|
||||||
|
BOCols.ORIGINE_RECRUTEMENT: "a_origine_recrutement",
|
||||||
|
BOCols.STATUT_CONCERTO: Cols.STATUT_CONCERTO,
|
||||||
|
BOCols.DATE_STATUT_CONCERTO: Cols.DATE_STATUT_CONCERTO,
|
||||||
|
BOCols.STATUT_CONCERTO_FUTUR: Cols.STATUT_CONCERTO_FUTUR,
|
||||||
|
BOCols.DATE_STATUT_CONCERTO_FUTUR: Cols.DATE_STATUT_CONCERTO_FUTUR,
|
||||||
|
BOCols.FUD: 'a_fud',
|
||||||
|
BOCols.DATE_FUD: 'a_date_fud',
|
||||||
|
BOCols.DATE_LIEN_SERVICE: "a_lien_service",
|
||||||
|
BOCols.AGE_ANNEES: "a_age_en_annees"
|
||||||
|
})
|
||||||
|
administres = administres.rename(columns=adm_mapping, errors='raise')
|
||||||
|
|
||||||
|
# initialisation des colonnes vides
|
||||||
|
adm_col_vides = {
|
||||||
|
'a_liste_id_marques': '',
|
||||||
|
'a_notes_gestionnaire': '',
|
||||||
|
'a_notes_partagees': '',
|
||||||
|
'a_flag_particulier': 0
|
||||||
|
}
|
||||||
|
for k, v in adm_col_vides.items():
|
||||||
|
administres[k] = v
|
||||||
|
|
||||||
|
administres = administres[list(adm_mapping.values()) + list(adm_col_vides.keys())]
|
||||||
|
|
||||||
|
administres_dom = administres.merge(domaines, how='inner', left_on=Cols.REL_DOMAINE, right_on='d_code')
|
||||||
|
df = pd.merge(administres, administres_dom, on=[Cols.PK, Cols.REL_DOMAINE], how="left", indicator=True)
|
||||||
|
df = df[df['_merge'] == 'left_only']
|
||||||
|
domaines_not_found = df[Cols.REL_DOMAINE].drop_duplicates().tolist()
|
||||||
|
administres_excluded_by_dom = df[Cols.PK].tolist()
|
||||||
|
logger.debug("Filtrage par domaine actuel reconnu")
|
||||||
|
logger.debug("Nombre d'administres : %s", administres_dom.shape[0])
|
||||||
|
logger.debug('Domaines non retrouvés : %s', domaines_not_found)
|
||||||
|
logger.debug('****************')
|
||||||
|
logger.debug(administres_excluded_by_dom)
|
||||||
|
logger.debug('****************')
|
||||||
|
administres_fil = administres_dom.merge(filieres, how='inner', left_on=Cols.REL_FILIERE, right_on='f_code')
|
||||||
|
# administres.merge(administres_fil, how='')
|
||||||
|
df = pd.merge(administres_dom, administres_fil, on=[Cols.PK, Cols.REL_FILIERE], how="left", indicator=True)
|
||||||
|
df = df[df['_merge'] == 'left_only']
|
||||||
|
filieres_not_found = df[Cols.REL_FILIERE].drop_duplicates().tolist()
|
||||||
|
administres_excluded_by_fil = df[Cols.PK].tolist()
|
||||||
|
logger.debug("Filtrage par filière reconnue")
|
||||||
|
logger.debug("Nombre d'administres restant : %s", administres_fil.shape[0])
|
||||||
|
logger.debug('Filières non retrouvées : %s', filieres_not_found)
|
||||||
|
logger.debug('****************')
|
||||||
|
logger.debug(administres_excluded_by_fil)
|
||||||
|
logger.debug('****************')
|
||||||
|
administres = administres_fil
|
||||||
|
|
||||||
|
administres = administres.merge(domaines, how='left', left_on='a_domaine_poste', right_on='d_code', indicator=True)
|
||||||
|
administres.loc[administres['_merge'] == 'left_only', 'a_domaine_poste'] = None
|
||||||
|
administres.drop(columns='_merge', inplace=True)
|
||||||
|
administres = administres.merge(filieres, how='left', left_on='a_filiere_poste', right_on='f_code', indicator=True)
|
||||||
|
administres.loc[administres['_merge'] == 'left_only', 'a_filiere_poste'] = None
|
||||||
|
# administres = administres.merge(filieres, how='inner', left_on='a_filiere_poste', right_on='f_code')
|
||||||
|
|
||||||
|
administres['a_categorie'] = administres["a_nf"].replace(
|
||||||
|
{'1A': 'MDR', '1B': 'MDR', '1C': 'MDR', '2.': 'SOFF', '3A': 'SOFF', '3B': 'SOFF', '3B NFS': 'SOFF', '4.': 'OFF',
|
||||||
|
'5A': 'OFF', '5B': 'OFF', '5C': 'OFF', '6A': 'OGX', '6B': 'OGX'}, inplace=False)
|
||||||
|
# administres["a_nom_prenom"].fillna('NOM Prenom', inplace=True)
|
||||||
|
# # administres["a_nom_prenom"].replace('', 'NOM Prenom', inplace=True)
|
||||||
|
# nom_prenom_split = administres['a_nom_prenom'].str.split(r" +", n=1).str
|
||||||
|
# administres["a_nom"] = nom_prenom_split.get(0).fillna('')
|
||||||
|
# administres["a_prenom"] = nom_prenom_split.get(1).fillna('')
|
||||||
|
# administres.drop("a_nom_prenom", inplace=True, axis=1)
|
||||||
|
administres['a_nom'] = administres['a_nom'].fillna('NOM')
|
||||||
|
administres['a_prenom'] = administres['a_prenom'].fillna('Prénom')
|
||||||
|
|
||||||
|
administres[f'{Cols.REL_FORMATION_EMPLOI}_id'] = administres[f'{Cols.REL_FORMATION_EMPLOI}_id'].replace({np.nan: None})
|
||||||
|
administres[f'{Cols.REL_GRADE}_id'] = administres[f'{Cols.REL_GRADE}_id'].replace({np.nan: None})
|
||||||
|
|
||||||
|
administres = administres.drop_duplicates(subset=[Cols.PK])
|
||||||
|
logger.debug('Retrait des doublons')
|
||||||
|
logger.debug("Nombre d'administres restants : %s", administres.shape[0])
|
||||||
|
|
||||||
|
administres = administres.reset_index(drop=True)
|
||||||
|
administres['a_sap_conjoint'] = administres['a_sap_conjoint'].replace({np.nan: None})
|
||||||
|
administres['a_nf_poste'] = (administres['a_nf_poste'].replace({np.nan, None})
|
||||||
|
.apply(lambda x: str(x)[1:])
|
||||||
|
.str.upper())
|
||||||
|
administres['a_nf'] = administres['a_nf'].str.upper()
|
||||||
|
administres['a_eip'] = administres[Cols.REL_DOMAINE] + administres[Cols.REL_FILIERE] + administres['a_nf']
|
||||||
|
administres['a_marqueur_pn'] = administres['a_marqueur_pn'].apply(lambda x: True if x == 'X' else False)
|
||||||
|
|
||||||
|
administres = (administres.fillna(np.nan)
|
||||||
|
.replace([np.nan], [None]))
|
||||||
|
logger.debug("Nombre d'administres extrait : %s", administres.shape[0])
|
||||||
|
return administres
|
||||||
|
|
||||||
|
|
||||||
20
backend-django/backend/utils/functions.py
Normal file
20
backend-django/backend/utils/functions.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
|
||||||
|
def find_class(func: Callable):
|
||||||
|
"""
|
||||||
|
basé sur inspect._findclass mais plus robuste et qui prend en compte les fonctions locales
|
||||||
|
note : la fonction doit avoir un module sinon None est renvoyé (ex : str => None)
|
||||||
|
"""
|
||||||
|
|
||||||
|
cls = sys.modules.get(getattr(func, '__module__', None))
|
||||||
|
if cls is None:
|
||||||
|
return None
|
||||||
|
for name in func.__qualname__.split('.')[:-1]:
|
||||||
|
if name != '<locals>':
|
||||||
|
cls = getattr(cls, name, None)
|
||||||
|
if not inspect.isclass(cls):
|
||||||
|
return None
|
||||||
|
return cls
|
||||||
187
backend-django/backend/utils/initial.py
Normal file
187
backend-django/backend/utils/initial.py
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
"""
|
||||||
|
Ce module contient les Utilitaires du backend
|
||||||
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
from random import randint
|
||||||
|
import re
|
||||||
|
from django.forms import model_to_dict
|
||||||
|
from backend import constants
|
||||||
|
from backend.models import Administre, Poste, SousVivier, FormationEmploi, RefSvFil, SousVivierAssociation
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def nf2categorie(nf: str) -> str:
|
||||||
|
"""
|
||||||
|
Renvoie la catégorie associé au niveau fonctionnel en argument
|
||||||
|
|
||||||
|
:type nf: str
|
||||||
|
:param nf: niveau fonctionnel
|
||||||
|
|
||||||
|
:return: catégorie associée au niveau fonctionnel
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
list_mdr = ['1A', '1B', '1C']
|
||||||
|
list_soff = ['2.', '3A', '3B', '3B NFS']
|
||||||
|
list_off = ['4.', '5A', '5B', '5C', '6A', '6B']
|
||||||
|
cat = 'MDR' if nf in list_mdr else 'SOFF' if nf in list_soff else 'OFF' if nf in list_off else None
|
||||||
|
return cat
|
||||||
|
|
||||||
|
|
||||||
|
def generate_sv_id(dom: str, fil: str, cat: str) -> str:
|
||||||
|
"""
|
||||||
|
Génère un id de sous-vivier à partir d'un domaine, d'une filière et d'une catégorie.
|
||||||
|
|
||||||
|
:type dom: str
|
||||||
|
:param dom: domaine du sous-vivier
|
||||||
|
|
||||||
|
:type fil: str
|
||||||
|
:param fil: filiere du sous-vivier
|
||||||
|
|
||||||
|
:type cat: str
|
||||||
|
:param cat: catégorie du sous-vivier
|
||||||
|
|
||||||
|
:return: id du sous-vivier
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
return f"{dom}, {fil}, {cat}"
|
||||||
|
|
||||||
|
|
||||||
|
def sous_viviers_du_cellule(cellule_code):
|
||||||
|
# Récupère les sous-viviers associés à la cellule
|
||||||
|
sous_vivier = RefSvFil.objects.filter(ref_sv_fil_code=cellule_code)
|
||||||
|
if sous_vivier.exists():
|
||||||
|
sous_viviers = list(sous_vivier.values_list('sous_vivier_id', flat=True))
|
||||||
|
else :
|
||||||
|
return []
|
||||||
|
return sous_viviers
|
||||||
|
|
||||||
|
|
||||||
|
def without_keys(d, keys):
|
||||||
|
"""
|
||||||
|
Suppression des clés d'un dictionnaire
|
||||||
|
|
||||||
|
:type d: Dictionnaire
|
||||||
|
:param d: Dictionnaire contenant les ids
|
||||||
|
|
||||||
|
:type keys: String
|
||||||
|
:param keys: Les clés que nous devons supprimer du dictionnaire
|
||||||
|
|
||||||
|
:return: - **Dictionnaire** (*Dictionnaire*): Dictionnaire sans les clés données en argument.
|
||||||
|
"""
|
||||||
|
return {x: d[x] for x in d if x not in keys}
|
||||||
|
|
||||||
|
def check_positive(valeur_nb):
|
||||||
|
"""
|
||||||
|
Vérifier si une valeur est positive et retourner 1, si elle est nulle ou négative retourner 0.
|
||||||
|
|
||||||
|
:type poste_nb: DataFrame
|
||||||
|
:param poste_nb: ID du sous-vivier
|
||||||
|
|
||||||
|
:return: - **valeur_nb_modifie** (*dataframe*): Dataframe contenant des valeurs 1 ou 0.
|
||||||
|
"""
|
||||||
|
valeur_nb_modifie = valeur_nb.apply(lambda x: 1 if x > 0 else 0)
|
||||||
|
return valeur_nb_modifie
|
||||||
|
|
||||||
|
def cleanString(string):
|
||||||
|
"""Cette fonction supprimera tous les caractères qui ne sont pas alphanumériques.
|
||||||
|
|
||||||
|
:type string: chaîne de caractères
|
||||||
|
:param string: chaîne qui doit être nettoyée
|
||||||
|
|
||||||
|
:return: - **return** (*chaîne de caractères*): chaîne nettoyée.
|
||||||
|
"""
|
||||||
|
return ''.join([i for i in string if i.isalnum()])
|
||||||
|
|
||||||
|
|
||||||
|
def intOrNone(value):
|
||||||
|
"""Cette fonction renvoie l'entier de la valeur ou renvoie None.
|
||||||
|
|
||||||
|
:type value: float
|
||||||
|
:param value: valeur a con
|
||||||
|
|
||||||
|
:return: - **res** (*int or None*): valeur à convertir.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
res = int(float(value))
|
||||||
|
except Exception:
|
||||||
|
res = None
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
def impact_decisions(old_administre, administre, old_avis, avis, eip, fe_code, categorie):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:type administre: objet
|
||||||
|
:param administre: instance du model Administre
|
||||||
|
|
||||||
|
:type old_avis: chaine de caractères
|
||||||
|
:param old_avis: avis de l'administré avant la mise à jour
|
||||||
|
|
||||||
|
:type avis: chaine de caractère
|
||||||
|
:param avis: nouvel avis de l'administré
|
||||||
|
|
||||||
|
:type eip: chaine de caractère
|
||||||
|
:param eip: eip actuel du militaire
|
||||||
|
|
||||||
|
:type categorie: dataframe pandas
|
||||||
|
:param categorie: Dataframe contenant les données pretraités à inserer
|
||||||
|
|
||||||
|
:return: - **list_error** (*liste*): liste des identifiants SAP pour lesquels il y a eu une erreur.
|
||||||
|
"""
|
||||||
|
|
||||||
|
list_error = []
|
||||||
|
if (old_avis == 'NON_ETUDIE' or old_avis == 'A_MAINTENIR' or old_avis == 'A_ETUDIER') and (avis == 'A_MUTER' or avis == 'PARTANT' or avis == 'NON_DISPONIBLE'):
|
||||||
|
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code)
|
||||||
|
poste = poste_qs.first()
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||||
|
fe.save()
|
||||||
|
|
||||||
|
if poste and poste.p_nb_non_etudie > 0:
|
||||||
|
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 + 1, poste.p_nb_non_etudie - 1, poste.p_nb_vacant + 1, poste.p_nb_occupe - 1
|
||||||
|
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
|
||||||
|
else:
|
||||||
|
list_error.append(administre.a_id_sap)
|
||||||
|
|
||||||
|
if (old_avis == 'A_MUTER' or old_avis == 'PARTANT' or old_avis == 'NON_DISPONIBLE') and (avis == 'NON_ETUDIE' or avis == 'A_MAINTENIR' or avis == 'A_ETUDIER'):
|
||||||
|
poste_qs = Poste.objects.filter(p_eip__iexact=eip, formation_emploi_id=fe_code).exclude(p_nb_occupe=0)
|
||||||
|
poste = poste_qs.first()
|
||||||
|
fe = FormationEmploi.objects.get(fe_code=fe_code)
|
||||||
|
if categorie == constants.CATEGORIE_MDR:
|
||||||
|
fe.fe_nb_poste_vacant_mdr = fe.fe_nb_poste_vacant_mdr - 1
|
||||||
|
fe.fe_nb_poste_occupe_mdr = fe.fe_nb_poste_occupe_mdr + 1
|
||||||
|
elif categorie == constants.CATEGORIE_SOFF:
|
||||||
|
fe.fe_nb_poste_vacant_soff = fe.fe_nb_poste_vacant_soff - 1
|
||||||
|
fe.fe_nb_poste_occupe_soff = fe.fe_nb_poste_occupe_soff + 1
|
||||||
|
elif categorie == constants.CATEGORIE_OFF:
|
||||||
|
fe.fe_nb_poste_vacant_off = fe.fe_nb_poste_vacant_off - 1
|
||||||
|
fe.fe_nb_poste_occupe_off = fe.fe_nb_poste_occupe_off + 1
|
||||||
|
|
||||||
|
fe.save()
|
||||||
|
|
||||||
|
if poste and poste.p_nb_p4 > 0:
|
||||||
|
new_nb_p4, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p4 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p4=new_nb_p4, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 > 0:
|
||||||
|
new_nb_p3, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p3 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p3=new_nb_p3, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 > 0:
|
||||||
|
new_nb_p2, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p2 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p2=new_nb_p2, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
if poste and poste.p_nb_p4 == 0 and poste.p_nb_p3 == 0 and poste.p_nb_p2 == 0 and poste.p_nb_p1 > 0:
|
||||||
|
new_nb_p1, new_nb_non_etudie, new_nb_vacant, new_nb_occupe = poste.p_nb_p1 - 1, poste.p_nb_non_etudie + 1, poste.p_nb_vacant - 1, poste.p_nb_occupe + 1
|
||||||
|
poste_qs.update(p_nb_p1=new_nb_p1, p_nb_non_etudie=new_nb_non_etudie,
|
||||||
|
p_nb_vacant=new_nb_vacant,
|
||||||
|
p_nb_occupe=new_nb_occupe)
|
||||||
|
else:
|
||||||
|
list_error.append(administre.a_id_sap)
|
||||||
|
return list_error
|
||||||
2
backend-django/backend/utils/insertion/__init__.py
Normal file
2
backend-django/backend/utils/insertion/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .administre import *
|
||||||
|
from .commun import *
|
||||||
448
backend-django/backend/utils/insertion/administre.py
Normal file
448
backend-django/backend/utils/insertion/administre.py
Normal file
@@ -0,0 +1,448 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pandas as pd
|
||||||
|
from backend.models.administre import Administre, Administres_Pams
|
||||||
|
from backend.models.administre import StatutPamChoices as StatutPam
|
||||||
|
from backend.models.pam import PAM
|
||||||
|
from backend.models.domaine import Domaine
|
||||||
|
from backend.models.filiere import Filiere
|
||||||
|
from backend.models.formation_emploi import FormationEmploi
|
||||||
|
from backend.models.grade import Grade
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
from ..alimentation_decorators import data_perf_logger_factory, get_data_logger
|
||||||
|
from ..decorators import execution_time
|
||||||
|
from .commun import (InsertionCounters, batch_iterator, is_same_model,
|
||||||
|
is_same_value)
|
||||||
|
|
||||||
|
logger = get_data_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@execution_time(level=logging.INFO, logger_factory=data_perf_logger_factory)
|
||||||
|
def update_administre_fmob(df) -> None:
|
||||||
|
"""
|
||||||
|
Met à jour le statut PAM des administrés quand un formulaire de mobilité est annulé.
|
||||||
|
"""
|
||||||
|
ModelType = Administres_Pams
|
||||||
|
Cols = ModelType.Cols
|
||||||
|
status = StatutPam.A_MAINTENIR
|
||||||
|
|
||||||
|
annee_pam = str(int(df['fmob_millesime'].iloc[0]))
|
||||||
|
|
||||||
|
|
||||||
|
updated = (ModelType.objects
|
||||||
|
.filter(**{f'{Cols.O2M_FMOB}__fmob_annulation_fmob': True})
|
||||||
|
.filter(~Q(**{Cols.STATUT_PAM: status}))
|
||||||
|
.filter(a_statut_pam_annee=StatutPam.NON_ETUDIE)
|
||||||
|
.filter(pam__pam_id=annee_pam)
|
||||||
|
.update(**{Cols.STATUT_PAM: status}))
|
||||||
|
if updated:
|
||||||
|
logger.info('%s | mis à jour car FMOB annulé : %s', ModelType.__name__, updated)
|
||||||
|
|
||||||
|
logger.info('Début de la mise à jour des FMOB en "A traiter"')
|
||||||
|
|
||||||
|
next_status = StatutPam.A_TRAITER
|
||||||
|
next_updated = (ModelType.objects
|
||||||
|
.filter(**{f'{Cols.O2M_FMOB}__isnull' : False})
|
||||||
|
.filter(~Q(**{Cols.STATUT_PAM: status}))
|
||||||
|
.filter(a_statut_pam_annee=StatutPam.NON_ETUDIE)
|
||||||
|
.filter(pam__pam_id=annee_pam)
|
||||||
|
.update(**{Cols.STATUT_PAM: next_status}))
|
||||||
|
if next_updated:
|
||||||
|
logger.info('%s | mis à jour car FMOB existe: %s administrés qui ont un FORMOB', ModelType.__name__, next_updated)
|
||||||
|
logger.info("%s[pk=%s] administré mis à jour", ModelType.__name__,next_updated)
|
||||||
|
|
||||||
|
return updated, next_updated
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@execution_time(level=logging.INFO, logger_factory=data_perf_logger_factory)
|
||||||
|
def insert_administre_bo(df: pd.DataFrame) -> None:
|
||||||
|
"""
|
||||||
|
Insère ou met à jour des données de la table des administrés.
|
||||||
|
|
||||||
|
:param df: Dataframe contenant les données pretraités à inserer
|
||||||
|
:type df: class:`pandas.DataFrame`
|
||||||
|
"""
|
||||||
|
logger.info('start update_administre')
|
||||||
|
ModelType = Administre
|
||||||
|
ModelType_2 = Administres_Pams
|
||||||
|
Cols = ModelType.Cols
|
||||||
|
Cols_2 = ModelType_2.Cols
|
||||||
|
col_pk = Cols.PK
|
||||||
|
fields_to_update = [
|
||||||
|
'formation_emploi_id',
|
||||||
|
'a_code_fonction',
|
||||||
|
'grade_id',
|
||||||
|
'a_grade_date_debut',
|
||||||
|
'a_fonction',
|
||||||
|
'a_nom',
|
||||||
|
'a_prenom',
|
||||||
|
'a_sexe',
|
||||||
|
'a_id_def',
|
||||||
|
'a_eip',
|
||||||
|
'a_eip_fiche_detaille',
|
||||||
|
'a_eis',
|
||||||
|
Cols.REL_DOMAINE,
|
||||||
|
Cols.REL_FILIERE,
|
||||||
|
'a_nf',
|
||||||
|
'a_domaine_gestion',
|
||||||
|
'a_date_entree_service',
|
||||||
|
'a_arme',
|
||||||
|
'a_rg_origine_recrutement',
|
||||||
|
'a_date_rdc',
|
||||||
|
'a_date_dernier_acr',
|
||||||
|
'a_date_naissance',
|
||||||
|
'a_diplome_hl',
|
||||||
|
'a_dernier_diplome',
|
||||||
|
'a_credo_fe',
|
||||||
|
'a_date_arrivee_fe',
|
||||||
|
'a_date_pos_statuaire',
|
||||||
|
'a_pos_statuaire',
|
||||||
|
'a_interruption_service',
|
||||||
|
'a_situation_fam',
|
||||||
|
'a_date_mariage',
|
||||||
|
'a_nombre_enfants',
|
||||||
|
'a_enfants',
|
||||||
|
'a_sap_conjoint',
|
||||||
|
'a_fonction1',
|
||||||
|
'a_fonction2',
|
||||||
|
'a_fonction3',
|
||||||
|
'a_fonction4',
|
||||||
|
'a_fonction5',
|
||||||
|
'a_fonction6',
|
||||||
|
'a_fonction7',
|
||||||
|
'a_fonction8',
|
||||||
|
'a_fonction9',
|
||||||
|
'a_date_fonction1',
|
||||||
|
'a_date_fonction2',
|
||||||
|
'a_date_fonction3',
|
||||||
|
'a_date_fonction4',
|
||||||
|
'a_date_fonction5',
|
||||||
|
'a_date_fonction6',
|
||||||
|
'a_date_fonction7',
|
||||||
|
'a_date_fonction8',
|
||||||
|
'a_date_fonction9',
|
||||||
|
'a_pls_gb_max',
|
||||||
|
'a_marqueur_pn',
|
||||||
|
'a_profession_conjoint',
|
||||||
|
'a_id_def_conjoint',
|
||||||
|
'a_sexe_conjoint',
|
||||||
|
'a_origine_recrutement',
|
||||||
|
Cols.STATUT_CONCERTO,
|
||||||
|
Cols.DATE_STATUT_CONCERTO,
|
||||||
|
Cols.STATUT_CONCERTO_FUTUR,
|
||||||
|
Cols.DATE_STATUT_CONCERTO_FUTUR,
|
||||||
|
'a_fud',
|
||||||
|
'a_date_fud',
|
||||||
|
'a_lien_service',
|
||||||
|
'a_categorie',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
fields_to_update_2 = [
|
||||||
|
'pam_id',
|
||||||
|
'administre_id',
|
||||||
|
'a_statut_pam_annee',
|
||||||
|
]
|
||||||
|
models_in_db = {m.pk: m for m in ModelType.objects.only(col_pk, *fields_to_update)}
|
||||||
|
fields_db = ['a_id_sap'] + fields_to_update
|
||||||
|
|
||||||
|
# Integer ou Float Colonnes à mettre à jour
|
||||||
|
fields_num = [
|
||||||
|
'a_sap_conjoint',
|
||||||
|
'a_pls_gb_max',
|
||||||
|
'a_nombre_enfants',
|
||||||
|
'a_id_sap'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Dict pour convertir toutes les colonnes non Integer en chaîne de caractères.
|
||||||
|
dict_conv_str = {a: str for a in fields_db if a not in fields_num}
|
||||||
|
|
||||||
|
# Dict pour convertir toutes les colonnes Integer en Float.
|
||||||
|
dict_conv_float = {a: float for a in fields_num }
|
||||||
|
|
||||||
|
# Lire tous les administrés de la base de données
|
||||||
|
adm_in_db_df = pd.DataFrame.from_records(Administre.objects.all().values_list(*tuple(fields_db)), columns = fields_db)
|
||||||
|
df = df.drop(['_merge'],1)
|
||||||
|
|
||||||
|
if not adm_in_db_df.empty :
|
||||||
|
# Il va y avoir de modification de type donc c'est mieux de ne pas toucher df
|
||||||
|
df_comparaison = df.copy()
|
||||||
|
|
||||||
|
# Modification de type de quelque champs
|
||||||
|
logger.debug('Conversion des types pour la fusion')
|
||||||
|
|
||||||
|
|
||||||
|
df_comparaison = df_comparaison.fillna(np.nan)
|
||||||
|
df_comparaison = df_comparaison.replace('None', np.nan)
|
||||||
|
adm_in_db_df = adm_in_db_df.fillna(np.nan)
|
||||||
|
adm_in_db_df = adm_in_db_df.replace('None', np.nan)
|
||||||
|
|
||||||
|
adm_in_db_df = adm_in_db_df.astype(dict_conv_str)
|
||||||
|
adm_in_db_df = adm_in_db_df.astype(dict_conv_float)
|
||||||
|
|
||||||
|
df_comparaison = df_comparaison.astype(dict_conv_str)
|
||||||
|
df_comparaison = df_comparaison.astype(dict_conv_float)
|
||||||
|
|
||||||
|
compare = pd.DataFrame([df_comparaison[fields_db].dtypes,adm_in_db_df[fields_db].dtypes]).T
|
||||||
|
logger.debug('Comparaison des types pour la fusion')
|
||||||
|
# logger.debug(compare[compare[0]!=compare[1]].dropna())
|
||||||
|
logger.debug('------------------------------------')
|
||||||
|
|
||||||
|
# Comparaison pour savoir ce qui doit etre creer, mis a jour ou supprimer
|
||||||
|
|
||||||
|
comparing_adm_sap = pd.merge(df_comparaison, adm_in_db_df, how='outer', on = 'a_id_sap', suffixes=(None, "_x"), indicator=True)
|
||||||
|
same_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'both'].drop('_merge', axis=1)
|
||||||
|
new_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'left_only'].drop('_merge', axis=1)
|
||||||
|
delete_rows = comparing_adm_sap[comparing_adm_sap['_merge'] == 'right_only'].drop('_merge', axis=1)
|
||||||
|
|
||||||
|
|
||||||
|
# Comparaison pour savoir des ligne a mettre a jour, lequel est deja a jour et lequel doit se mettre a jour
|
||||||
|
|
||||||
|
comparing_adm_both = pd.merge(same_rows, adm_in_db_df, how='left', on=fields_db,
|
||||||
|
suffixes=(None, "_x"), indicator=True)
|
||||||
|
|
||||||
|
not_updated_rows = comparing_adm_both[comparing_adm_both['_merge'] == 'both'].drop(['_merge'], axis=1)
|
||||||
|
updated_rows = comparing_adm_both[comparing_adm_both['_merge'] == 'left_only'].drop(['_merge'], axis=1)
|
||||||
|
|
||||||
|
# Creation du df final avec une colonne db_create_status qui dis si la ligne doit etre creer ou mis a jour
|
||||||
|
|
||||||
|
update = df.loc[df['a_id_sap'].isin(list(updated_rows['a_id_sap']))]
|
||||||
|
update['db_create_status'] = 0
|
||||||
|
create = df.loc[df['a_id_sap'].isin(list(new_rows['a_id_sap']))]
|
||||||
|
create['db_create_status'] = 1
|
||||||
|
|
||||||
|
df = pd.concat([update,create])
|
||||||
|
|
||||||
|
|
||||||
|
else :
|
||||||
|
df['db_create_status'] = 1
|
||||||
|
not_updated_rows = pd.DataFrame([])
|
||||||
|
|
||||||
|
|
||||||
|
# IDs existants pour les clés étrangères
|
||||||
|
|
||||||
|
domaines_in_db = set(Domaine.objects.values_list('pk', flat=True))
|
||||||
|
filieres_in_db = set(Filiere.objects.values_list('pk', flat=True))
|
||||||
|
formations_in_db = set(FormationEmploi.objects.values_list('pk', flat=True))
|
||||||
|
grades_in_db = set(Grade.objects.values_list('pk', flat=True))
|
||||||
|
|
||||||
|
fields_not_validated = [f.name for f in ModelType._meta.get_fields() if f.is_relation]
|
||||||
|
fields_not_validated_2 = [f.name for f in ModelType_2._meta.get_fields() if f.is_relation]
|
||||||
|
|
||||||
|
dict_create = {}
|
||||||
|
dict_update = {}
|
||||||
|
dict_up_to_date = {}
|
||||||
|
dict_create_2 = {}
|
||||||
|
set_dom = set()
|
||||||
|
set_fil = set()
|
||||||
|
counters = InsertionCounters()
|
||||||
|
|
||||||
|
annee_pam = PAM.objects.filter(pam_statut='PAM en cours')[0].pam_id
|
||||||
|
annee_pam_suivant = PAM.objects.filter(pam_statut='PAM A+1')[0].pam_id
|
||||||
|
|
||||||
|
def process_row(row):
|
||||||
|
pk = str(row[col_pk])
|
||||||
|
pk_2 = pk + str(annee_pam)
|
||||||
|
pk_3 = pk + str(annee_pam_suivant)
|
||||||
|
try:
|
||||||
|
domaine = row['a_domaine']
|
||||||
|
if domaine is not None and domaine not in domaines_in_db:
|
||||||
|
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||||
|
logger.warning("%s[pk=%s] domaine ignoré car absent du référentiel : %s", ModelType.__name__, pk, domaine)
|
||||||
|
set_dom.add(domaine)
|
||||||
|
counters.ignored += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
filiere = row['a_filiere']
|
||||||
|
if filiere is not None and filiere not in filieres_in_db:
|
||||||
|
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||||
|
logger.warning("%s[pk=%s] filière ignoré car absente du référentiel : %s", ModelType.__name__, pk, filiere)
|
||||||
|
set_fil.add(filiere)
|
||||||
|
counters.ignored += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
grade = row['grade_id']
|
||||||
|
if grade is not None and grade not in grades_in_db:
|
||||||
|
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||||
|
logger.warning("%s[pk=%s] ignoré car grade inconnu : %s", ModelType.__name__, pk, grade)
|
||||||
|
counters.ignored += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
formation = row['formation_emploi_id']
|
||||||
|
if formation is not None and formation not in formations_in_db:
|
||||||
|
# TODO déjà fait dans l'extraction, serait mieux placé ici
|
||||||
|
logger.warning("%s[pk=%s] ignoré car formation-emploi inconnue : %s", ModelType.__name__, pk, formation)
|
||||||
|
counters.ignored += 1
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
model_2 = ModelType_2(**{
|
||||||
|
'id' :pk_2,
|
||||||
|
'pam_id' :annee_pam,
|
||||||
|
'administre_id' :pk,
|
||||||
|
'a_statut_pam_annee' :StatutPam.NON_ETUDIE,
|
||||||
|
})
|
||||||
|
|
||||||
|
model_3 = ModelType_2(**{
|
||||||
|
'id' :pk_3,
|
||||||
|
'pam_id' :annee_pam_suivant,
|
||||||
|
'administre_id' :pk,
|
||||||
|
'a_statut_pam_annee' :StatutPam.NON_ETUDIE,
|
||||||
|
})
|
||||||
|
|
||||||
|
in_db = models_in_db.get(pk)
|
||||||
|
model = ModelType(**{
|
||||||
|
'pk': pk,
|
||||||
|
'formation_emploi_id': formation,
|
||||||
|
'grade_id': grade,
|
||||||
|
'a_grade_date_debut': row['a_grade_date_debut'],
|
||||||
|
'a_liste_id_marques': row['a_liste_id_marques'],
|
||||||
|
'a_nom': row['a_nom'],
|
||||||
|
'a_prenom': row['a_prenom'],
|
||||||
|
'a_sexe': row['a_sexe'],
|
||||||
|
'a_id_def': row['a_id_def'],
|
||||||
|
'a_eip': row['a_eip'],
|
||||||
|
'a_eip_fiche_detaille': row['a_eip_fiche_detaille'],
|
||||||
|
f'{Cols.REL_DOMAINE}_id': domaine,
|
||||||
|
'a_domaine_futur_id': domaine,
|
||||||
|
f'{Cols.REL_FILIERE}_id': filiere,
|
||||||
|
'a_filiere_futur_id': filiere,
|
||||||
|
'a_fonction': row['a_fonction'],
|
||||||
|
'a_code_fonction': row['a_code_fonction'],
|
||||||
|
'a_nf': row['a_nf'],
|
||||||
|
'a_nf_futur': row['a_nf'],
|
||||||
|
'a_categorie': row['a_categorie'],
|
||||||
|
'a_domaine_gestion': row['a_domaine_gestion'],
|
||||||
|
'a_date_entree_service': row['a_date_entree_service'],
|
||||||
|
'a_arme': row['a_arme'],
|
||||||
|
'a_rg_origine_recrutement': row['a_rg_origine_recrutement'],
|
||||||
|
'a_date_naissance': row['a_date_naissance'],
|
||||||
|
'a_diplome_hl': row['a_diplome_hl'],
|
||||||
|
'a_dernier_diplome': row['a_dernier_diplome'],
|
||||||
|
'a_credo_fe': row['a_credo_fe'],
|
||||||
|
'a_date_arrivee_fe': row['a_date_arrivee_fe'],
|
||||||
|
'a_date_pos_statuaire': row['a_date_pos_statuaire'],
|
||||||
|
'a_pos_statuaire': row['a_pos_statuaire'],
|
||||||
|
'a_interruption_service': row['a_interruption_service'],
|
||||||
|
'a_situation_fam': row['a_situation_fam'],
|
||||||
|
'a_date_mariage': row['a_date_mariage'],
|
||||||
|
'a_nombre_enfants': row['a_nombre_enfants'],
|
||||||
|
'a_enfants': row['a_enfants'],
|
||||||
|
'a_date_rdc': row['a_date_rdc'],
|
||||||
|
'a_date_dernier_acr': row['a_date_dernier_acr'],
|
||||||
|
'a_eis': row['a_eis'],
|
||||||
|
'a_sap_conjoint': row['a_sap_conjoint'],
|
||||||
|
'a_flag_particulier': row['a_flag_particulier'],
|
||||||
|
'a_notes_gestionnaire': row['a_notes_gestionnaire'],
|
||||||
|
'a_notes_partagees': row['a_notes_partagees'],
|
||||||
|
'a_fonction1': row['a_fonction1'],
|
||||||
|
'a_fonction2': row['a_fonction2'],
|
||||||
|
'a_fonction3': row['a_fonction3'],
|
||||||
|
'a_fonction4': row['a_fonction4'],
|
||||||
|
'a_fonction5': row['a_fonction5'],
|
||||||
|
'a_fonction6': row['a_fonction6'],
|
||||||
|
'a_fonction7': row['a_fonction7'],
|
||||||
|
'a_fonction8': row['a_fonction8'],
|
||||||
|
'a_fonction9': row['a_fonction9'],
|
||||||
|
'a_date_fonction1': row['a_date_fonction1'],
|
||||||
|
'a_date_fonction2': row['a_date_fonction2'],
|
||||||
|
'a_date_fonction3': row['a_date_fonction3'],
|
||||||
|
'a_date_fonction4': row['a_date_fonction4'],
|
||||||
|
'a_date_fonction5': row['a_date_fonction5'],
|
||||||
|
'a_date_fonction6': row['a_date_fonction6'],
|
||||||
|
'a_date_fonction7': row['a_date_fonction7'],
|
||||||
|
'a_date_fonction8': row['a_date_fonction8'],
|
||||||
|
'a_date_fonction9': row['a_date_fonction9'],
|
||||||
|
'a_pls_gb_max': row['a_pls_gb_max'],
|
||||||
|
'a_marqueur_pn': row['a_marqueur_pn'],
|
||||||
|
'a_profession_conjoint': row['a_profession_conjoint'],
|
||||||
|
'a_id_def_conjoint': row['a_id_def_conjoint'],
|
||||||
|
'a_sexe_conjoint': row['a_sexe_conjoint'],
|
||||||
|
'a_origine_recrutement': row['a_origine_recrutement'],
|
||||||
|
'a_lien_service': row['a_lien_service'],
|
||||||
|
'a_statut_concerto': row[Cols.STATUT_CONCERTO],
|
||||||
|
'a_date_statut_concerto': row[Cols.DATE_STATUT_CONCERTO],
|
||||||
|
'a_statut_concerto_futur': row[Cols.STATUT_CONCERTO_FUTUR],
|
||||||
|
'a_date_statut_concerto_futur': row[Cols.DATE_STATUT_CONCERTO_FUTUR],
|
||||||
|
'a_fud': row['a_fud'],
|
||||||
|
'a_date_fud': row['a_date_fud'],
|
||||||
|
})
|
||||||
|
if row['db_create_status']:
|
||||||
|
model_3.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||||
|
dict_create_2.setdefault(pk_3, model_3)
|
||||||
|
|
||||||
|
model_2.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||||
|
dict_create_2.setdefault(pk_2, model_2)
|
||||||
|
|
||||||
|
model.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||||
|
dict_create.setdefault(pk, model)
|
||||||
|
else:
|
||||||
|
if not ModelType_2.objects.filter(Q(id=pk_2)).exists():
|
||||||
|
model_2.full_clean(exclude=fields_not_validated_2, validate_unique=False)
|
||||||
|
dict_create_2.setdefault(pk_2, model_2)
|
||||||
|
|
||||||
|
if not ModelType_2.objects.filter(Q(id=pk_3)).exists():
|
||||||
|
model_3.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||||
|
dict_create_2.setdefault(pk_3, model_3)
|
||||||
|
model.full_clean(exclude=fields_not_validated, validate_unique=False)
|
||||||
|
dict_update.setdefault(pk, model)
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
counters.errors += 1
|
||||||
|
logger.exception('%s une erreur est survenue à la ligne : %s (pk=%s)', ModelType.__name__, row['index'], pk)
|
||||||
|
|
||||||
|
df['index'] = df.index
|
||||||
|
df.apply(process_row, axis=1)
|
||||||
|
|
||||||
|
if counters.errors:
|
||||||
|
logger.warning("%s | en erreur : %s", ModelType.__name__, counters.errors)
|
||||||
|
|
||||||
|
if counters.ignored:
|
||||||
|
logger.warning('%s | ignoré(s) : %s', ModelType.__name__, counters.ignored)
|
||||||
|
|
||||||
|
if set_dom:
|
||||||
|
logger.warning('%s %s(s) ignoré(s) : %s', len(set_dom), Domaine.__name__, set_dom)
|
||||||
|
|
||||||
|
if set_fil:
|
||||||
|
logger.warning('%s %s(s) ignorée(s) : %s', len(set_fil), Filiere.__name__, set_fil)
|
||||||
|
|
||||||
|
if not not_updated_rows.empty and fields_to_update:
|
||||||
|
logger.info('%s | déjà à jour : %s', ModelType.__name__, len(not_updated_rows))
|
||||||
|
|
||||||
|
batch_size = 50
|
||||||
|
if dict_create:
|
||||||
|
logger.info('%s | à créer : %s', ModelType.__name__, len(dict_create))
|
||||||
|
for idx, data_batch in enumerate(batch_iterator(list(dict_create.values()), batch_size)):
|
||||||
|
ModelType.objects.bulk_create(data_batch)
|
||||||
|
logger.debug('créé(s) : %s (lot %s)', len(data_batch), idx + 1)
|
||||||
|
logger.info('%s | créé(s) : %s', ModelType.__name__, len(dict_create))
|
||||||
|
else :
|
||||||
|
logger.info('%s | rien à créer', ModelType.__name__)
|
||||||
|
|
||||||
|
if dict_create_2:
|
||||||
|
logger.info('%s | à créer dans la vue A+1 : (%s)', ModelType_2.__name__, len(dict_create_2))
|
||||||
|
for idx, data_batch in enumerate(batch_iterator(list(dict_create_2.values()), batch_size)):
|
||||||
|
ModelType_2.objects.bulk_create(data_batch)
|
||||||
|
logger.debug('créé(s) : %s (lot %s)', len(data_batch), idx + 1)
|
||||||
|
logger.info('%s | créé(s) : %s', ModelType_2.__name__, len(dict_create_2))
|
||||||
|
|
||||||
|
if dict_update and fields_to_update:
|
||||||
|
logger.info('%s | à mettre à jour : %s', ModelType.__name__, len(dict_update))
|
||||||
|
for idx, data_batch in enumerate(batch_iterator(list(dict_update.values()), batch_size)):
|
||||||
|
ModelType.objects.bulk_update(data_batch, fields=fields_to_update)
|
||||||
|
logger.debug('mis à jour : %s (lot %s)', len(data_batch), idx + 1)
|
||||||
|
logger.info('%s | mis à jour : %s', ModelType.__name__, len(dict_update))
|
||||||
|
else :
|
||||||
|
logger.info('%s | rien à mettre à jour', ModelType.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
adm_cree = len(dict_create) + len(dict_create_2)
|
||||||
|
adm_modifie = len(dict_update)
|
||||||
|
return adm_cree, adm_modifie, len(not_updated_rows), counters.errors, counters.ignored, set_dom, set_fil
|
||||||
|
|
||||||
|
# pas de suppression ici, il est prévu de faire un upload de fichier spécifique
|
||||||
|
|
||||||
61
backend-django/backend/utils/insertion/commun.py
Normal file
61
backend-django/backend/utils/insertion/commun.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from typing import Any, List, Tuple, Union
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
from django.db.models import Choices, Model
|
||||||
|
|
||||||
|
from ..alimentation_decorators import get_data_logger
|
||||||
|
|
||||||
|
logger = get_data_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def batch_iterator(iterable: Union[List, Tuple, pd.DataFrame], batch_size: int) -> Union[List, Tuple, pd.DataFrame]:
|
||||||
|
"""
|
||||||
|
(je pense que le nom _batch_generator porterait à confusion)
|
||||||
|
Générateur qui morcelle un itérable en lots de taille donnée.
|
||||||
|
|
||||||
|
:param iterable: itérable (supported un data frame)
|
||||||
|
:type iterable: Union[List,Tuple,pd.DataFrame]
|
||||||
|
|
||||||
|
:param batch_size: taille de lot
|
||||||
|
:type batch_size: int
|
||||||
|
|
||||||
|
:return: lot (valide, utilisateur, nouveau)
|
||||||
|
:rtype: Tuple[bool, Optional[UserType], bool]
|
||||||
|
"""
|
||||||
|
length = len(iterable)
|
||||||
|
for idx in range(0, length, batch_size):
|
||||||
|
if isinstance(iterable, pd.DataFrame):
|
||||||
|
yield iterable.iloc[idx:min(idx + batch_size, length)]
|
||||||
|
else:
|
||||||
|
yield iterable[idx:min(idx + batch_size, length)]
|
||||||
|
|
||||||
|
|
||||||
|
def is_same_value(val1: Any, val2: Any) -> bool:
|
||||||
|
"""
|
||||||
|
Indique si deux valeurs sont équivalentes. Le cas le plus courant est une instance de Choices comparée à sa valeur.
|
||||||
|
"""
|
||||||
|
_1 = val1 if isinstance(val1, Choices) else val1
|
||||||
|
_2 = val2 if isinstance(val2, Choices) else val2
|
||||||
|
return _1 == _2
|
||||||
|
|
||||||
|
|
||||||
|
def is_same_model(fields: Union[List[str], Tuple[str]], m1: Model, m2: Model) -> bool:
|
||||||
|
"""
|
||||||
|
Indique si deux modèles contiennent les mêmes données. Ce test permet de limiter les MAJ.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _get_value(m: Model, field: str):
|
||||||
|
""" Récupère la valeur d'un champ """
|
||||||
|
attr = getattr(m, field)
|
||||||
|
return attr.value if isinstance(attr, Choices) else attr
|
||||||
|
|
||||||
|
return not all(is_same_value(getattr(m1, f), getattr(m2, f)) for f in fields)
|
||||||
|
|
||||||
|
|
||||||
|
class InsertionCounters:
|
||||||
|
"""
|
||||||
|
conteneur de compteurs pour faciliter l'incrémentation dans des fonctions imbriquées
|
||||||
|
"""
|
||||||
|
|
||||||
|
ignored = 0
|
||||||
|
errors = 0
|
||||||
126
backend-django/backend/utils/logging.py
Normal file
126
backend-django/backend/utils/logging.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import inspect
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
from logging import Filter, Logger, LoggerAdapter, getLogger
|
||||||
|
from typing import Any, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
|
from .functions import find_class
|
||||||
|
|
||||||
|
# nom du module de fonction built-in
|
||||||
|
BUILTIN_MODULE = 'builtins'
|
||||||
|
|
||||||
|
# tag pour l'alimentation
|
||||||
|
TAG_DATA_FEED = 'alimentation'
|
||||||
|
|
||||||
|
# tag pour la performance
|
||||||
|
TAG_PERF = 'performance'
|
||||||
|
|
||||||
|
# clé du dictionnaire 'kwargs' (standard pour 'logging')
|
||||||
|
KEY_EXTRA = 'extra'
|
||||||
|
|
||||||
|
# clé du dictionnaire 'extra' (le type attendu est une liste ou un tuple)
|
||||||
|
KEY_EXTRA_TAGS = 'tags'
|
||||||
|
|
||||||
|
# cache des adaptateurs
|
||||||
|
cache_adapters = {}
|
||||||
|
|
||||||
|
|
||||||
|
def __get_full_name(module_name: Optional[str] = None, cls_name: Optional[str] = None) -> Optional[str]:
|
||||||
|
""" renvoie le nom complet à partir du nom de module et du nom de classe """
|
||||||
|
|
||||||
|
if module_name or cls_name:
|
||||||
|
return f'{module_name}.{cls_name}' if module_name and cls_name else cls_name or module_name
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger_name(value: Any = None) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Renvoie un nom de logger pour la valeur donnée :
|
||||||
|
- None ou chaîne vide : None
|
||||||
|
- chaîne non vide : la valeur initiale
|
||||||
|
- un objet sans module ou de module BUILTIN_MODULE : None (utiliser une chaîne pour forcer le nom)
|
||||||
|
- classe, instance de classe, fonction ou méthode : <module>.<nom qualifié de la classe> si possible, sinon <module>
|
||||||
|
|
||||||
|
:param valeur: valeur initiale
|
||||||
|
:type valeur: Any, optional
|
||||||
|
|
||||||
|
:return: nom de logger
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
if value is None or isinstance(value, str):
|
||||||
|
return value or None
|
||||||
|
|
||||||
|
module = inspect.getmodule(value)
|
||||||
|
module_name = module.__name__ if module else None
|
||||||
|
if not module_name or module_name == BUILTIN_MODULE:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if inspect.isclass(value):
|
||||||
|
return __get_full_name(module_name, value.__qualname__)
|
||||||
|
|
||||||
|
if isinstance(value, (
|
||||||
|
types.BuiltinFunctionType,
|
||||||
|
types.BuiltinMethodType,
|
||||||
|
types.ClassMethodDescriptorType,
|
||||||
|
types.FunctionType,
|
||||||
|
types.MethodDescriptorType,
|
||||||
|
types.MethodType,
|
||||||
|
)):
|
||||||
|
cls = find_class(value)
|
||||||
|
return __get_full_name(module_name, cls.__qualname__ if cls else None)
|
||||||
|
|
||||||
|
return __get_full_name(module_name, value.__class__.__qualname__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(value: Any = None, tags: Union[str, List[str], Set[str], Tuple[str]] = None) -> Union[Logger, LoggerAdapter]:
|
||||||
|
"""
|
||||||
|
Renvoie un logger standard avec un nom obtenu par 'get_logger_name'
|
||||||
|
|
||||||
|
:param valeur: valeur initiale
|
||||||
|
:type valeur: Any, optional
|
||||||
|
|
||||||
|
:param tags: tag(s) à ajouter dans le contexte de log
|
||||||
|
:type tags: Union[str, List[str], Set[str], Tuple[str]], optional
|
||||||
|
|
||||||
|
:raises TypeError: type inattendu
|
||||||
|
|
||||||
|
:return: logger
|
||||||
|
:rtype: class:`logging.Logger` ou class:`logging.LoggerAdapter`
|
||||||
|
"""
|
||||||
|
logger_name = get_logger_name(value)
|
||||||
|
if not tags:
|
||||||
|
return getLogger(logger_name)
|
||||||
|
|
||||||
|
adapters = cache_adapters.setdefault(tags if isinstance(tags, str) else frozenset(tags), {})
|
||||||
|
return adapters.get(logger_name) or adapters.setdefault(logger_name, TagLoggerAdapter(getLogger(logger_name), {KEY_EXTRA_TAGS: tags}))
|
||||||
|
|
||||||
|
|
||||||
|
class TagLoggerAdapter(LoggerAdapter):
|
||||||
|
"""
|
||||||
|
Adaptateur qui ajout si nécessaire des tags.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, logger, extra):
|
||||||
|
super().__init__(logger, extra)
|
||||||
|
if not isinstance(self.extra, dict):
|
||||||
|
self.extra = {}
|
||||||
|
tags = self.extra.setdefault(KEY_EXTRA_TAGS, frozenset())
|
||||||
|
if not isinstance(tags, frozenset):
|
||||||
|
self.extra.update({KEY_EXTRA_TAGS: frozenset(tags) if isinstance(tags, (list, set, tuple)) else frozenset([tags])})
|
||||||
|
|
||||||
|
def process(self, msg, kwargs):
|
||||||
|
""" ajoute les tags au contexte si nécessaire """
|
||||||
|
tags = self.extra.get(KEY_EXTRA_TAGS)
|
||||||
|
if tags:
|
||||||
|
kw_extra = kwargs.setdefault(KEY_EXTRA, {})
|
||||||
|
kw_tags = kw_extra.setdefault(KEY_EXTRA_TAGS, set())
|
||||||
|
if not isinstance(kw_tags, (list, tuple, set)):
|
||||||
|
kw_tags = set(kw_tags)
|
||||||
|
kw_extra.update({KEY_EXTRA_TAGS: kw_tags})
|
||||||
|
for tag in tags:
|
||||||
|
if isinstance(kw_tags, set):
|
||||||
|
kw_tags.add(tag)
|
||||||
|
elif tag not in kw_tags:
|
||||||
|
kw_tags = set(*kw_tags, tag)
|
||||||
|
kw_extra.update({KEY_EXTRA_TAGS: kw_tags})
|
||||||
|
return msg, kwargs
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user