diff --git a/README.md b/README.md
index f3e6293..c4af8a0 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,24 @@
# AutoSco
-Composant satellite de ScoDoc pour l'auto-inscription des étudiants
\ No newline at end of file
+Composant satellite de ScoDoc pour l'auto-inscription des étudiants
+
+## Configuration de l'accès à ScoDoc
+
+Côté ScoDoc, créer un rôle et un utilisateur dédiés:
+
+```bash
+flask create-role AutoSco
+flask edit-role AutoSco -a ScoView
+flask user-create autosco AutoSco @all
+flask user-password autosco
+```
+
+Configurer les paramètres d'accès dans AutoSco: éditer le fichier
+`/opt/autosco/.env` et indiquer
+
+```bash
+SCODOC_URL="http://localhost:5000" # l'URL racine de votre ScoDoc
+SCODOC_LOGIN="autosco"
+SCODOC_PASSWORD="xxx" # le mot de passe saisi ci-dessus
+```
+
diff --git a/app/templates/index.j2 b/app/templates/index.j2
new file mode 100644
index 0000000..10c7d4a
--- /dev/null
+++ b/app/templates/index.j2
@@ -0,0 +1,17 @@
+{# Page accueil #}
+{% extends 'base.j2' %}
+
+{% block content %}
+
+
AutoSco - Accueil
+
+
+{% for sem in sems %}
+
+ {{sem.date_debut}} : {{sem.titre}}
+
+{% endfor %}
+
+
+{% endblock %}
+
diff --git a/app/utils/utils.py b/app/utils/utils.py
index d0ed8fd..c71c7e4 100644
--- a/app/utils/utils.py
+++ b/app/utils/utils.py
@@ -2,9 +2,36 @@
"""
import os
+import time
import version
# le répertoire static, lié à chaque release pour éviter les problèmes de caches
STATIC_DIR = (
os.environ.get("SCRIPT_NAME", "") + "/AutoSco/static/links/" + version.VERSION
)
+
+
+# Dates et années scolaires
+# Ces dates "pivot" sont paramétrables dans les préférences générales
+# on donne ici les valeurs par défaut.
+# Les semestres commençant à partir du 1er août 20XX sont
+# dans l'année scolaire 20XX
+MONTH_DEBUT_ANNEE_SCOLAIRE = 8 # août
+
+
+def annee_scolaire() -> int:
+ """Année de debut de l'annee scolaire courante"""
+ t = time.localtime()
+ year, month = t[0], t[1]
+ return annee_scolaire_debut(year, month)
+
+
+def annee_scolaire_debut(year, month) -> int:
+ """Annee scolaire de début.
+ Par défaut (hémisphère nord), l'année du mois de août
+ précédent la date indiquée.
+ """
+ if int(month) >= MONTH_DEBUT_ANNEE_SCOLAIRE:
+ return int(year)
+ else:
+ return int(year) - 1
diff --git a/app/views/views.py b/app/views/views.py
index 3a51732..6c7ab87 100644
--- a/app/views/views.py
+++ b/app/views/views.py
@@ -1,9 +1,17 @@
"""AutoSco / views
"""
+from flask import render_template
+from app.utils import utils as scu
from app.views import bp
+from scodoc import api
@bp.route("/")
def index():
- return "hello"
+ annee_scolaire = scu.annee_scolaire()
+ sems = api.get(f"/formsemestres/query?annee_scolaire={annee_scolaire}")
+ # nb: l'utilisaton de l'API départementale permet de n'avoir
+ # que les semestres du département configuré.
+
+ return render_template("index.j2", sems=sems)
diff --git a/config.py b/config.py
index 10b2b85..a718315 100644
--- a/config.py
+++ b/config.py
@@ -33,6 +33,20 @@ class Config:
JSON_ADD_STATUS = False
JSON_USE_ENCODE_METHODS = True
JSON_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S%z" # "%Y-%m-%dT%H:%M:%S"
+ # --- Lien avec API ScoDoc
+ SCODOC_URL = os.environ.get("SCODOC_URL", "http://localhost:5000")
+ SCODOC_LOGIN = os.environ.get("SCODOC_LOGIN", "autosco")
+ SCODOC_PASSWORD = os.environ.get("SCODOC_PASSWORD")
+ SCODOC_CHECK_CERTIFICATE = os.environ.get("SCODOC_CHECK_CERTIFICATE", True)
+ API_TIMEOUT = 120 # 2 minutes
+ API_URL = SCODOC_URL + "/ScoDoc/api"
+ SCODOC_DEPT_ACRONYM = "ESPL"
+
+ def __getitem__(self, k) -> str | int | None:
+ return getattr(self, k)
+
+ def get(self, k, default=None):
+ return getattr(self, k, default=default)
class ProdConfig(Config):
diff --git a/scodoc/__init__.py b/scodoc/__init__.py
new file mode 100644
index 0000000..4b81c3c
--- /dev/null
+++ b/scodoc/__init__.py
@@ -0,0 +1 @@
+"""AutoSco / Lien avec ScoDoc"""
diff --git a/scodoc/api.py b/scodoc/api.py
new file mode 100644
index 0000000..23ae7b3
--- /dev/null
+++ b/scodoc/api.py
@@ -0,0 +1,126 @@
+"""AutoSco: ScoDoc API usage
+"""
+
+import os
+import requests
+from flask import current_app
+import config
+
+
+def get_auth_headers(user: str, password: str, conf: config.Config) -> dict:
+ "Demande de jeton, dict à utiliser dans les en-têtes de requêtes http"
+ ans = requests.post(
+ conf["API_URL"] + "/tokens",
+ auth=(user, password),
+ timeout=conf["API_TIMEOUT"],
+ )
+ if ans.status_code != 200:
+ raise APIError(f"Echec demande jeton par {user}", status_code=ans.status_code)
+ token = ans.json()["token"]
+ return {"Authorization": f"Bearer {token}"}
+
+
+class APIError(Exception):
+ def __init__(self, message: str = "", payload=None, status_code=None):
+ self.message = message
+ self.payload = payload or {}
+ self.status_code = status_code
+
+ def __str__(self):
+ return f"APIError: {self.message} payload={self.payload} status_code={self.status_code}"
+
+
+class APIAccessor:
+ "Gestion bas niveau des accès à l'API ScoDoc"
+
+ def __init__(self, conf: config.Config, dept_acronym: str | None = None):
+ self.config = conf
+ self.dept_acronym = dept_acronym
+ "si spécifié, utilisera API départementale"
+ user = self.config["SCODOC_LOGIN"]
+ password = self.config["SCODOC_PASSWORD"]
+ self.headers = get_auth_headers(user, password, self.config)
+
+ def get(self, path: str, headers: dict = None, errmsg=None, dept=None, raw=False):
+ """Get.
+ If raw, returns a a requests.Response
+ Else retrns decoded json or, if other content, requests.Response
+ Special case for non json result (image or pdf):
+ return Content-Disposition string (inline or attachment)
+ If raw, return
+ """
+ dept = dept or self.dept_acronym
+ if dept:
+ url = self.config["SCODOC_URL"] + f"/ScoDoc/{dept}/api" + path
+ else:
+ url = self.config["API_URL"] + path
+ reply = requests.get(
+ url,
+ headers=self.headers if headers is None else headers,
+ verify=self.config["SCODOC_CHECK_CERTIFICATE"],
+ timeout=self.config["API_TIMEOUT"],
+ )
+ if reply.status_code != 200:
+ print("url", url)
+ print("reply", reply.text)
+ try:
+ payload = r.json()
+ except requests.exceptions.JSONDecodeError:
+ payload = r.text
+ raise APIError(
+ errmsg or f"""erreur get {url} !""",
+ payload,
+ status_code=reply.status_code,
+ )
+ if (not raw) and reply.headers.get("Content-Type", None) == "application/json":
+ return reply.json() # decode la reponse JSON
+ return reply
+
+ def post(
+ self,
+ path: str,
+ data: dict = None,
+ headers: dict = None,
+ errmsg=None,
+ dept=None,
+ raw=False,
+ ):
+ """Post
+ Decode réponse en json, sauf si raw.
+ """
+ data = data or {}
+ dept = dept or self.dept_acronym
+ if dept:
+ url = self.config["SCODOC_URL"] + f"/ScoDoc/{dept}/api" + path
+ else:
+ url = self.config["API_URL"] + path
+ r = requests.post(
+ url,
+ json=data,
+ headers=self.headers if headers is None else headers,
+ verify=self.config["SCODOC_CHECK_CERTIFICATE"],
+ timeout=self.config["API_TIMEOUT"],
+ )
+ if r.status_code != 200:
+ try:
+ payload = r.json()
+ except requests.exceptions.JSONDecodeError:
+ payload = r.text
+ raise APIError(
+ errmsg or f"erreur url={url} status={r.status_code} !",
+ payload=payload,
+ status_code=r.status_code,
+ )
+ if (not raw) and reply.headers.get("Content-Type", None) == "application/json":
+ return r.json() # decode la reponse JSON
+
+ return r
+
+
+def get(path, conf: config.Config = None):
+ """Connect to ScoDoc and get.
+ Utilise département configuré dans la config.
+ """
+ conf = conf or (current_app.config if current_app else config.RunningConfig())
+ apicnx = APIAccessor(conf, dept_acronym=conf["SCODOC_DEPT_ACRONYM"])
+ return apicnx.get(path)