ositah 25.6.dev1__py3-none-any.whl → 25.9.dev2__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.
- ositah/app.py +17 -17
- ositah/apps/analysis.py +785 -785
- ositah/apps/configuration/callbacks.py +916 -916
- ositah/apps/configuration/main.py +546 -546
- ositah/apps/configuration/parameters.py +74 -74
- ositah/apps/configuration/tools.py +112 -112
- ositah/apps/export.py +1209 -1191
- ositah/apps/validation/callbacks.py +240 -240
- ositah/apps/validation/main.py +89 -89
- ositah/apps/validation/parameters.py +25 -25
- ositah/apps/validation/tables.py +646 -646
- ositah/apps/validation/tools.py +552 -552
- ositah/assets/arrow_down_up.svg +3 -3
- ositah/assets/ositah.css +53 -53
- ositah/assets/sort_ascending.svg +4 -4
- ositah/assets/sort_descending.svg +5 -5
- ositah/assets/sorttable.js +499 -499
- ositah/main.py +449 -449
- ositah/ositah.example.cfg +229 -229
- ositah/static/style.css +53 -53
- ositah/templates/base.html +22 -22
- ositah/templates/bootstrap_login.html +38 -38
- ositah/templates/login_form.html +26 -26
- ositah/utils/agents.py +124 -124
- ositah/utils/authentication.py +287 -287
- ositah/utils/cache.py +19 -19
- ositah/utils/core.py +13 -13
- ositah/utils/exceptions.py +64 -64
- ositah/utils/hito_db.py +51 -51
- ositah/utils/hito_db_model.py +253 -253
- ositah/utils/menus.py +339 -339
- ositah/utils/period.py +139 -139
- ositah/utils/projects.py +1179 -1178
- ositah/utils/teams.py +42 -42
- ositah/utils/utils.py +474 -474
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/METADATA +149 -150
- ositah-25.9.dev2.dist-info/RECORD +46 -0
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/licenses/LICENSE +29 -29
- ositah-25.6.dev1.dist-info/RECORD +0 -46
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/WHEEL +0 -0
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.dist-info}/entry_points.txt +0 -0
- {ositah-25.6.dev1.dist-info → ositah-25.9.dev2.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">© 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">© 2017-2018</p>
|
|
36
|
+
</form>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
ositah/templates/login_form.html
CHANGED
|
@@ -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
|