ScoDoc/app/views/scodoc.py

321 lines
11 KiB
Python
Raw Normal View History

2021-07-04 12:32:13 +02:00
# -*- mode: python -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# ScoDoc
#
2022-01-01 14:49:42 +01:00
# Copyright (c) 1999 - 2022 Emmanuel Viennet. All rights reserved.
2021-07-04 12:32:13 +02:00
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Emmanuel Viennet emmanuel.viennet@viennet.net
#
##############################################################################
"""
Module main: page d'accueil, avec liste des départements
Emmanuel Viennet, 2021
"""
2021-12-11 17:20:34 +01:00
import datetime
import io
2021-11-16 18:48:56 +01:00
import wtforms.validators
from app.auth.models import User
import os
2021-07-04 12:32:13 +02:00
import flask
from flask import abort, flash, url_for, redirect, render_template, send_file
2021-07-28 09:51:18 +02:00
from flask import request
from flask.app import Flask
import flask_login
from flask_login.utils import login_required, current_user
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from werkzeug.exceptions import BadRequest, NotFound
2021-11-19 11:51:05 +01:00
from wtforms import SelectField, SubmitField, FormField, validators, Form, FieldList
from wtforms.fields import IntegerField
2021-11-16 18:48:56 +01:00
from wtforms.fields.simple import BooleanField, StringField, TextAreaField, HiddenField
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
import app
2022-01-04 20:03:38 +01:00
from app import db
from app.forms.main import config_forms
from app.forms.main.create_dept import CreateDeptForm
from app.models import Departement, Identite
from app.models import departements
from app.models import FormSemestre, FormSemestreInscription
2021-08-21 17:07:44 +02:00
import sco_version
from app.scodoc import sco_logos
2021-07-28 09:51:18 +02:00
from app.scodoc import sco_find_etud
from app.scodoc import sco_utils as scu
from app.decorators import (
admin_required,
scodoc7func,
scodoc,
permission_required_compat_scodoc7,
2021-11-19 11:51:05 +01:00
permission_required,
)
from app.scodoc.sco_exceptions import AccessDenied
2021-11-16 18:48:56 +01:00
from app.scodoc.sco_logos import find_logo
2021-07-04 12:32:13 +02:00
from app.scodoc.sco_permissions import Permission
from app.views import scodoc_bp as bp
2021-07-04 12:32:13 +02:00
from PIL import Image as PILImage
2021-07-04 12:32:13 +02:00
2021-08-17 22:11:35 +02:00
@bp.route("/")
2021-07-04 12:32:13 +02:00
@bp.route("/ScoDoc")
@bp.route("/ScoDoc/index")
def index():
"Page d'accueil: liste des départements"
2022-01-04 20:03:38 +01:00
depts = Departement.query.filter_by().order_by(Departement.acronym).all()
2021-07-04 12:32:13 +02:00
return render_template(
"scodoc.html",
2021-08-21 17:07:44 +02:00
title=sco_version.SCONAME,
2021-07-04 12:32:13 +02:00
current_app=flask.current_app,
depts=depts,
2021-07-04 12:32:13 +02:00
Permission=Permission,
)
2021-07-28 09:51:18 +02:00
# Renvoie les url /ScoDoc/RT/ vers /ScoDoc/RT/Scolarite
@bp.route("/ScoDoc/<scodoc_dept>/")
def index_dept(scodoc_dept):
return redirect(url_for("scolar.index_html", scodoc_dept=scodoc_dept))
@bp.route("/ScoDoc/create_dept", methods=["GET", "POST"])
@admin_required
def create_dept():
"""Form création département"""
form = CreateDeptForm()
if request.method == "POST" and form.cancel.data: # cancel button
return redirect(url_for("scodoc.index"))
if form.validate_on_submit():
2022-01-04 20:03:38 +01:00
departements.create_dept(form.acronym.data, visible=form.visible.data)
flash(f"Département {form.acronym.data} créé.")
return redirect(url_for("scodoc.index"))
return render_template(
"create_dept.html",
form=form,
title="Création d'un nouveau département",
)
2022-01-04 20:03:38 +01:00
@bp.route("/ScoDoc/toggle_dept_vis/<dept_id>", methods=["GET", "POST"])
@admin_required
def toggle_dept_vis(dept_id):
"""Cache ou rend visible un dept"""
dept = Departement.query.get_or_404(dept_id)
dept.visible = not dept.visible
db.session.add(dept)
db.session.commit()
return redirect(url_for("scodoc.index"))
2021-07-28 09:51:18 +02:00
@bp.route("/ScoDoc/table_etud_in_accessible_depts", methods=["POST"])
@login_required
def table_etud_in_accessible_depts():
"""recherche étudiants sur plusieurs départements"""
return sco_find_etud.table_etud_in_accessible_depts(expnom=request.form["expnom"])
2021-08-29 19:57:32 +02:00
# Fonction d'API accessible sans aucun authentification
@bp.route("/ScoDoc/get_etud_dept")
def get_etud_dept():
"""Returns the dept acronym (eg "GEII") of an etud (identified by etudid,
code_nip ou code_ine in the request).
Ancienne API: ramène la chaine brute, texte sans JSON ou XML.
"""
if "etudid" in request.args:
# zero ou une réponse:
etuds = [Identite.query.get(request.args["etudid"])]
elif "code_nip" in request.args:
# il peut y avoir plusieurs réponses si l'étudiant est passé par plusieurs départements
etuds = Identite.query.filter_by(code_nip=request.args["code_nip"]).all()
elif "code_ine" in request.args:
2021-10-25 15:51:11 +02:00
etuds = Identite.query.filter_by(code_ine=request.args["code_ine"]).all()
else:
raise BadRequest(
"missing argument (expected one among: etudid, code_nip or code_ine)"
)
if not etuds:
raise NotFound("student not found")
elif len(etuds) == 1:
last_etud = etuds[0]
else:
# inscriptions dans plusieurs departements: cherche la plus recente
last_etud = None
last_date = None
for etud in etuds:
2021-12-22 14:31:44 +01:00
inscriptions = FormSemestreInscription.query.filter_by(etudid=etud.id).all()
for ins in inscriptions:
date_fin = FormSemestre.query.get(ins.formsemestre_id).date_fin
if (last_date is None) or date_fin > last_date:
last_date = date_fin
last_etud = etud
if not last_etud:
# est présent dans plusieurs semestres mais inscrit dans aucun !
# le choix a peu d'importance...
last_etud = etuds[-1]
return Departement.query.get(last_etud.dept_id).acronym
# Bricolage pour le portail IUTV avec ScoDoc 7: (DEPRECATED: NE PAS UTILISER !)
@bp.route(
"/ScoDoc/search_inscr_etud_by_nip", methods=["GET"]
) # pour compat anciens clients PHP
@scodoc
@scodoc7func
def search_inscr_etud_by_nip(code_nip, format="json", __ac_name="", __ac_password=""):
auth_ok = False
user_name = __ac_name
user_password = __ac_password
if user_name and user_password:
u = User.query.filter_by(user_name=user_name).first()
if u and u.check_password(user_password):
auth_ok = True
flask_login.login_user(u)
if not auth_ok:
abort(403)
else:
return sco_find_etud.search_inscr_etud_by_nip(code_nip=code_nip, format=format)
2021-09-11 15:59:06 +02:00
@bp.route("/ScoDoc/about")
@bp.route("/ScoDoc/Scolarite/<scodoc_dept>/about")
def about(scodoc_dept=None):
"version info"
return render_template(
"about.html",
version=scu.get_scodoc_version(),
news=sco_version.SCONEWS,
logo=scu.icontag("borgne_img"),
)
# ---- CONFIGURATION
# Notes pour variables config: (valeurs par défaut des paramètres de département)
# Chaines simples
# SCOLAR_FONT = "Helvetica"
# SCOLAR_FONT_SIZE = 10
# SCOLAR_FONT_SIZE_FOOT = 6
# INSTITUTION_NAME = "<b>Institut Universitaire de Technologie - Université Georges Perec</b>"
# INSTITUTION_ADDRESS = "Web <b>www.sor.bonne.top</b> - 11, rue Simon Crubelier - 75017 Paris"
# INSTITUTION_CITY = "Paris"
# Textareas:
# DEFAULT_PDF_FOOTER_TEMPLATE = "Edité par %(scodoc_name)s le %(day)s/%(month)s/%(year)s à %(hour)sh%(minute)s sur %(server_url)s"
# Booléens
# always_require_ine
# Logos:
# LOGO_FOOTER*, LOGO_HEADER*
@bp.route("/ScoDoc/configuration", methods=["GET", "POST"])
@admin_required
def configuration():
2021-11-19 11:51:05 +01:00
auth_name = str(current_user)
if not current_user.is_administrator():
raise AccessDenied("invalid user (%s) must be SuperAdmin" % auth_name)
return config_forms.configuration()
2021-11-19 11:51:05 +01:00
SMALL_SIZE = (200, 200)
def _return_logo(name="header", dept_id="", small=False, strict: bool = True):
# stockée dans /opt/scodoc-data/config/logos donc servie manuellement ici
2021-12-11 17:55:08 +01:00
# from app.scodoc.sco_photos import _http_jpeg_file
logo = sco_logos.find_logo(name, dept_id, strict).select()
if logo is not None:
suffix = logo.suffix
if small:
with PILImage.open(logo.filepath) as im:
im.thumbnail(SMALL_SIZE)
stream = io.BytesIO()
# on garde le même format (on pourrait plus simplement générer systématiquement du JPEG)
fmt = { # adapt suffix to be compliant with PIL save format
"PNG": "PNG",
"JPG": "JPEG",
"JPEG": "JPEG",
}[suffix.upper()]
im.save(stream, fmt)
stream.seek(0)
return send_file(stream, mimetype=f"image/{fmt}")
else:
2021-12-11 17:55:08 +01:00
# return _http_jpeg_file(logo.filepath)
# ... replaces ...
2021-12-11 17:20:34 +01:00
return send_file(
logo.filepath,
mimetype=f"image/{suffix}",
last_modified=datetime.datetime.now(),
)
else:
abort(404)
# small version (copy/paste from get_logo
@bp.route("/ScoDoc/logos/<name>/small", defaults={"dept_id": None})
@bp.route("/ScoDoc/<int:dept_id>/logos/<name>/small")
@admin_required
def get_logo_small(name: str, dept_id: int):
strict = request.args.get("strict", "False")
return _return_logo(
name,
dept_id=dept_id,
small=True,
strict=strict.upper() not in ["0", "FALSE"],
)
@bp.route(
"/ScoDoc/logos/<name>", defaults={"dept_id": None}
) # if dept not specified, take global logo
@bp.route("/ScoDoc/<int:dept_id>/logos/<name>")
@admin_required
def get_logo(name: str, dept_id: int):
strict = request.args.get("strict", "False")
return _return_logo(
name,
dept_id=dept_id,
small=False,
strict=strict.upper() not in ["0", "FALSE"],
)
2021-08-29 19:57:32 +02:00
# essais
2021-08-29 22:42:38 +02:00
# @bp.route("/testlog")
# def testlog():
# import time
# from flask import current_app
# from app import log
2021-08-29 19:57:32 +02:00
2021-08-29 22:42:38 +02:00
# log(f"testlog called: handlers={current_app.logger.handlers}")
# current_app.logger.debug(f"testlog message DEBUG")
# current_app.logger.info(f"testlog message INFO")
# current_app.logger.warning(f"testlog message WARNING")
# current_app.logger.error(f"testlog message ERROR")
# current_app.logger.critical(f"testlog message CRITICAL")
# raise SyntaxError("une erreur de syntaxe")
# return "testlog completed at " + str(time.time())