ositah 25.6.dev1__py3-none-any.whl → 25.9.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ositah might be problematic. Click here for more details.

Files changed (42) hide show
  1. ositah/app.py +17 -17
  2. ositah/apps/analysis.py +785 -785
  3. ositah/apps/configuration/callbacks.py +916 -916
  4. ositah/apps/configuration/main.py +546 -546
  5. ositah/apps/configuration/parameters.py +74 -74
  6. ositah/apps/configuration/tools.py +112 -112
  7. ositah/apps/export.py +1208 -1191
  8. ositah/apps/validation/callbacks.py +240 -240
  9. ositah/apps/validation/main.py +89 -89
  10. ositah/apps/validation/parameters.py +25 -25
  11. ositah/apps/validation/tables.py +646 -646
  12. ositah/apps/validation/tools.py +552 -552
  13. ositah/assets/arrow_down_up.svg +3 -3
  14. ositah/assets/ositah.css +53 -53
  15. ositah/assets/sort_ascending.svg +4 -4
  16. ositah/assets/sort_descending.svg +5 -5
  17. ositah/assets/sorttable.js +499 -499
  18. ositah/main.py +449 -449
  19. ositah/ositah.example.cfg +229 -229
  20. ositah/static/style.css +53 -53
  21. ositah/templates/base.html +22 -22
  22. ositah/templates/bootstrap_login.html +38 -38
  23. ositah/templates/login_form.html +26 -26
  24. ositah/utils/agents.py +124 -124
  25. ositah/utils/authentication.py +287 -287
  26. ositah/utils/cache.py +19 -19
  27. ositah/utils/core.py +13 -13
  28. ositah/utils/exceptions.py +64 -64
  29. ositah/utils/hito_db.py +51 -51
  30. ositah/utils/hito_db_model.py +253 -253
  31. ositah/utils/menus.py +339 -339
  32. ositah/utils/period.py +139 -139
  33. ositah/utils/projects.py +1178 -1178
  34. ositah/utils/teams.py +42 -42
  35. ositah/utils/utils.py +474 -474
  36. {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/METADATA +149 -150
  37. ositah-25.9.dev1.dist-info/RECORD +46 -0
  38. {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/licenses/LICENSE +29 -29
  39. ositah-25.6.dev1.dist-info/RECORD +0 -46
  40. {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/WHEEL +0 -0
  41. {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/entry_points.txt +0 -0
  42. {ositah-25.6.dev1.dist-info → ositah-25.9.dev1.dist-info}/top_level.txt +0 -0
@@ -1,38 +1,38 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
- <meta name="description" content="">
7
- <meta name="author" content="">
8
- <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">
9
-
10
- <title>Signin Template for Bootstrap</title>
11
-
12
- <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/sign-in/">
13
-
14
- <!-- Bootstrap core CSS -->
15
- <link href="../../dist/css/bootstrap.min.css" rel="stylesheet">
16
-
17
- <!-- Custom styles for this template -->
18
- <link href="signin.css" rel="stylesheet">
19
- </head>
20
-
21
- <body class="text-center">
22
- <form class="form-signin">
23
- <img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
24
- <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
25
- <label for="inputEmail" class="sr-only">Email address</label>
26
- <input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
27
- <label for="inputPassword" class="sr-only">Password</label>
28
- <input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
29
- <div class="checkbox mb-3">
30
- <label>
31
- <input type="checkbox" value="remember-me"> Remember me
32
- </label>
33
- </div>
34
- <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
35
- <p class="mt-5 mb-3 text-muted">&copy; 2017-2018</p>
36
- </form>
37
- </body>
38
- </html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
6
+ <meta name="description" content="">
7
+ <meta name="author" content="">
8
+ <link rel="icon" href="/docs/4.0/assets/img/favicons/favicon.ico">
9
+
10
+ <title>Signin Template for Bootstrap</title>
11
+
12
+ <link rel="canonical" href="https://getbootstrap.com/docs/4.0/examples/sign-in/">
13
+
14
+ <!-- Bootstrap core CSS -->
15
+ <link href="../../dist/css/bootstrap.min.css" rel="stylesheet">
16
+
17
+ <!-- Custom styles for this template -->
18
+ <link href="signin.css" rel="stylesheet">
19
+ </head>
20
+
21
+ <body class="text-center">
22
+ <form class="form-signin">
23
+ <img class="mb-4" src="https://getbootstrap.com/docs/4.0/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
24
+ <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
25
+ <label for="inputEmail" class="sr-only">Email address</label>
26
+ <input type="email" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
27
+ <label for="inputPassword" class="sr-only">Password</label>
28
+ <input type="password" id="inputPassword" class="form-control" placeholder="Password" required>
29
+ <div class="checkbox mb-3">
30
+ <label>
31
+ <input type="checkbox" value="remember-me"> Remember me
32
+ </label>
33
+ </div>
34
+ <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
35
+ <p class="mt-5 mb-3 text-muted">&copy; 2017-2018</p>
36
+ </form>
37
+ </body>
38
+ </html>
@@ -1,27 +1,27 @@
1
- <div class="login_page">
2
- {% extends 'base.html' %}
3
- {% block content %}
4
- <p>Log in using <strong>{{ provider.title }}</strong></p>
5
-
6
- <form method=post>
7
- <div class="login_form">
8
- {% for field in form %}
9
- {% if field.widget.__class__.__name__ == 'HiddenInput' %}
10
- {{ field() }}
11
- {% else %}
12
- <div class="login_form_field">
13
- {{ field.label() }}
14
- </div>
15
- <div>
16
- {{ field() }}
17
- {% if field.errors %}
18
- Errors: {{ field.errors|join(' / ') }}
19
- {% endif %}
20
- </div>
21
- {% endif %}
22
- {% endfor %}
23
- <input type="submit" value="Login" class="btn btn-secondary login_button">
24
- </div>
25
- </form>
26
- {% endblock %}
1
+ <div class="login_page">
2
+ {% extends 'base.html' %}
3
+ {% block content %}
4
+ <p>Log in using <strong>{{ provider.title }}</strong></p>
5
+
6
+ <form method=post>
7
+ <div class="login_form">
8
+ {% for field in form %}
9
+ {% if field.widget.__class__.__name__ == 'HiddenInput' %}
10
+ {{ field() }}
11
+ {% else %}
12
+ <div class="login_form_field">
13
+ {{ field.label() }}
14
+ </div>
15
+ <div>
16
+ {{ field() }}
17
+ {% if field.errors %}
18
+ Errors: {{ field.errors|join(' / ') }}
19
+ {% endif %}
20
+ </div>
21
+ {% endif %}
22
+ {% endfor %}
23
+ <input type="submit" value="Login" class="btn btn-secondary login_button">
24
+ </div>
25
+ </form>
26
+ {% endblock %}
27
27
  </div>
ositah/utils/agents.py CHANGED
@@ -1,124 +1,124 @@
1
- # Helper functions to interact with the agent table
2
- import pandas as pd
3
- from flask import session
4
-
5
- from ositah.utils.exceptions import SessionDataMissing
6
- from ositah.utils.utils import TEAM_LIST_ALL_AGENTS, GlobalParams, no_session_id_jumbotron
7
-
8
- NSIP_AGENT_COLUMNS = {
9
- "email_reseda": "email_reseda",
10
- "firstname": "firstname",
11
- "lastname": "lastname",
12
- }
13
-
14
-
15
- def get_agent_by_email(agent_email: str = None) -> str:
16
- """
17
- Retrieve an agent from Hito using his email. If the agent email argument
18
- is not present, use the session user_email attribute.
19
-
20
- :param: agent_email: agent's email
21
- :return: agent entry (row)
22
- """
23
-
24
- from ositah.utils.hito_db_model import Agent
25
-
26
- if agent_email is None:
27
- agent_email = session["user_email"]
28
- user = Agent.query.filter_by(email_auth=agent_email).first()
29
- if user is None:
30
- user = Agent.query.filter_by(email=session["user_email"]).first()
31
-
32
- return user
33
-
34
-
35
- def get_agents(period_date: str, team: str = None) -> pd.DataFrame:
36
- """
37
- Read agent table in Hito database and return it as a dataframe. If a cached version exists,
38
- use it.
39
-
40
- :param period_date: a date that must be inside the declaration period
41
- :param team: selected team (and subteams). Ignored if None or TEAM_LIST_ALL_AGENTS.
42
- :return: dataframe
43
- """
44
-
45
- from ositah.utils.hito_db import get_db
46
-
47
- global_params = GlobalParams()
48
- columns = global_params.columns
49
- db = get_db()
50
-
51
- try:
52
- session_data = global_params.session_data
53
- except SessionDataMissing:
54
- return no_session_id_jumbotron()
55
-
56
- # start_date, _ = get_validation_period_dates(period_date)
57
-
58
- agents = session_data.agent_list
59
- if agents is None:
60
- agent_list = pd.read_sql_query(global_params.agent_query, con=db.engine)
61
- # WIP ; attempt to refine the agent list taking into account arrival and departure date
62
- # Difficulty: carriere table has a suboptimal structure making a SQL request difficult
63
- # TODO: query separately agent+team and carriere, then use Pandas to join them taking all
64
- # the criteria into account
65
- # Ignore archived agents that left before the start of the declaration period
66
- # agent_list = agent_list.loc[(agent_list.archive == 0) |
67
- # (agent_list.date_fin >= start_date.date().isoformat())]
68
- if team and team != TEAM_LIST_ALL_AGENTS:
69
- agent_list = agent_list[
70
- agent_list.team.notna() & agent_list[columns["team"]].str.match(team)
71
- ]
72
- else:
73
- # If the agent doesn't belong to a team, set team to an empty string rather than None
74
- agent_list.loc[agent_list.team.isna(), "team"] = ""
75
- agent_list[columns["fullname"]] = agent_list[
76
- [columns["lastname"], columns["firstname"]]
77
- ].agg(" ".join, axis=1)
78
- agent_list.columns = agent_list.columns.str.lower()
79
- agent_list["statut"] = agent_list["statut"].str.extract("^statut_(.+)$")
80
- agent_list["optional"] = agent_list["statut"].isin(
81
- global_params.declaration_options["optional_statutes"]
82
- )
83
- agent_list["email_auth"] = agent_list["email_auth"].str.lower()
84
- team_list = pd.DataFrame(agent_list[columns["team"]].unique(), columns=["name"])
85
- optional_teams_list = []
86
- for opt_team in global_params.declaration_options["optional_teams"]:
87
- optional_teams_list.append(
88
- team_list[team_list["name"].str.match(opt_team, case=False, na=False)]["name"]
89
- )
90
- optional_teams = pd.concat(optional_teams_list)
91
- agent_list.loc[~agent_list["optional"], "optional"] = agent_list["team"].isin(
92
- optional_teams
93
- )
94
- session_data.agent_list = agent_list
95
- else:
96
- agent_list = session_data.agent_list
97
-
98
- return agent_list
99
-
100
-
101
- def get_nsip_agents():
102
- """
103
- Retrieve agents from NSIP and return them in a dataframe.
104
-
105
- :return: dataframe or None if NSIP is not configured
106
- """
107
-
108
- global_params = GlobalParams()
109
-
110
- if global_params.nsip:
111
- agents = pd.DataFrame.from_dict(global_params.nsip.get_agent_list())
112
- columns_to_delete = []
113
- for c in agents.columns.tolist():
114
- if c not in NSIP_AGENT_COLUMNS.values():
115
- columns_to_delete.append(c)
116
- agents["fullname"] = agents[
117
- [NSIP_AGENT_COLUMNS["lastname"], NSIP_AGENT_COLUMNS["firstname"]]
118
- ].agg(" ".join, axis=1)
119
- agents.drop(columns=columns_to_delete, inplace=True)
120
-
121
- return agents
122
-
123
- else:
124
- return None
1
+ # Helper functions to interact with the agent table
2
+ import pandas as pd
3
+ from flask import session
4
+
5
+ from ositah.utils.exceptions import SessionDataMissing
6
+ from ositah.utils.utils import TEAM_LIST_ALL_AGENTS, GlobalParams, no_session_id_jumbotron
7
+
8
+ NSIP_AGENT_COLUMNS = {
9
+ "email_reseda": "email_reseda",
10
+ "firstname": "firstname",
11
+ "lastname": "lastname",
12
+ }
13
+
14
+
15
+ def get_agent_by_email(agent_email: str = None) -> str:
16
+ """
17
+ Retrieve an agent from Hito using his email. If the agent email argument
18
+ is not present, use the session user_email attribute.
19
+
20
+ :param: agent_email: agent's email
21
+ :return: agent entry (row)
22
+ """
23
+
24
+ from ositah.utils.hito_db_model import Agent
25
+
26
+ if agent_email is None:
27
+ agent_email = session["user_email"]
28
+ user = Agent.query.filter_by(email_auth=agent_email).first()
29
+ if user is None:
30
+ user = Agent.query.filter_by(email=session["user_email"]).first()
31
+
32
+ return user
33
+
34
+
35
+ def get_agents(period_date: str, team: str = None) -> pd.DataFrame:
36
+ """
37
+ Read agent table in Hito database and return it as a dataframe. If a cached version exists,
38
+ use it.
39
+
40
+ :param period_date: a date that must be inside the declaration period
41
+ :param team: selected team (and subteams). Ignored if None or TEAM_LIST_ALL_AGENTS.
42
+ :return: dataframe
43
+ """
44
+
45
+ from ositah.utils.hito_db import get_db
46
+
47
+ global_params = GlobalParams()
48
+ columns = global_params.columns
49
+ db = get_db()
50
+
51
+ try:
52
+ session_data = global_params.session_data
53
+ except SessionDataMissing:
54
+ return no_session_id_jumbotron()
55
+
56
+ # start_date, _ = get_validation_period_dates(period_date)
57
+
58
+ agents = session_data.agent_list
59
+ if agents is None:
60
+ agent_list = pd.read_sql_query(global_params.agent_query, con=db.engine)
61
+ # WIP ; attempt to refine the agent list taking into account arrival and departure date
62
+ # Difficulty: carriere table has a suboptimal structure making a SQL request difficult
63
+ # TODO: query separately agent+team and carriere, then use Pandas to join them taking all
64
+ # the criteria into account
65
+ # Ignore archived agents that left before the start of the declaration period
66
+ # agent_list = agent_list.loc[(agent_list.archive == 0) |
67
+ # (agent_list.date_fin >= start_date.date().isoformat())]
68
+ if team and team != TEAM_LIST_ALL_AGENTS:
69
+ agent_list = agent_list[
70
+ agent_list.team.notna() & agent_list[columns["team"]].str.match(team)
71
+ ]
72
+ else:
73
+ # If the agent doesn't belong to a team, set team to an empty string rather than None
74
+ agent_list.loc[agent_list.team.isna(), "team"] = ""
75
+ agent_list[columns["fullname"]] = agent_list[
76
+ [columns["lastname"], columns["firstname"]]
77
+ ].agg(" ".join, axis=1)
78
+ agent_list.columns = agent_list.columns.str.lower()
79
+ agent_list["statut"] = agent_list["statut"].str.extract("^statut_(.+)$")
80
+ agent_list["optional"] = agent_list["statut"].isin(
81
+ global_params.declaration_options["optional_statutes"]
82
+ )
83
+ agent_list["email_auth"] = agent_list["email_auth"].str.lower()
84
+ team_list = pd.DataFrame(agent_list[columns["team"]].unique(), columns=["name"])
85
+ optional_teams_list = []
86
+ for opt_team in global_params.declaration_options["optional_teams"]:
87
+ optional_teams_list.append(
88
+ team_list[team_list["name"].str.match(opt_team, case=False, na=False)]["name"]
89
+ )
90
+ optional_teams = pd.concat(optional_teams_list)
91
+ agent_list.loc[~agent_list["optional"], "optional"] = agent_list["team"].isin(
92
+ optional_teams
93
+ )
94
+ session_data.agent_list = agent_list
95
+ else:
96
+ agent_list = session_data.agent_list
97
+
98
+ return agent_list
99
+
100
+
101
+ def get_nsip_agents():
102
+ """
103
+ Retrieve agents from NSIP and return them in a dataframe.
104
+
105
+ :return: dataframe or None if NSIP is not configured
106
+ """
107
+
108
+ global_params = GlobalParams()
109
+
110
+ if global_params.nsip:
111
+ agents = pd.DataFrame.from_dict(global_params.nsip.get_agent_list())
112
+ columns_to_delete = []
113
+ for c in agents.columns.tolist():
114
+ if c not in NSIP_AGENT_COLUMNS.values():
115
+ columns_to_delete.append(c)
116
+ agents["fullname"] = agents[
117
+ [NSIP_AGENT_COLUMNS["lastname"], NSIP_AGENT_COLUMNS["firstname"]]
118
+ ].agg(" ".join, axis=1)
119
+ agents.drop(columns=columns_to_delete, inplace=True)
120
+
121
+ return agents
122
+
123
+ else:
124
+ return None