diff --git a/app/api/jury.py b/app/api/jury.py
index 294eef2f6..9d4aad56b 100644
--- a/app/api/jury.py
+++ b/app/api/jury.py
@@ -10,7 +10,7 @@
import datetime
-from flask import flash, g, request, url_for
+from flask import g, request, url_for
from flask_json import as_json
from flask_login import current_user, login_required
diff --git a/app/scodoc/sco_cal.py b/app/scodoc/sco_cal.py
new file mode 100644
index 000000000..25179c016
--- /dev/null
+++ b/app/scodoc/sco_cal.py
@@ -0,0 +1,511 @@
+# -*- mode: python -*-
+# -*- coding: utf-8 -*-
+
+##############################################################################
+#
+# Gestion scolarite IUT
+#
+# Copyright (c) 1999 - 2023 Emmanuel Viennet. All rights reserved.
+#
+# 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
+#
+##############################################################################
+
+"""Génération calendrier (ancienne présentation)
+"""
+
+import calendar
+import datetime
+import html
+import time
+
+from app.scodoc.sco_exceptions import ScoValueError, ScoInvalidDateError
+from app.scodoc import sco_preferences
+import app.scodoc.sco_utils as scu
+
+
+def is_work_saturday():
+ "Vrai si le samedi est travaillé"
+ return int(sco_preferences.get_preference("work_saturday"))
+
+
+def MonthNbDays(month, year):
+ "returns nb of days in month"
+ if month > 7:
+ month = month + 1
+ if month % 2:
+ return 31
+ elif month == 2:
+ if calendar.isleap(year):
+ return 29
+ else:
+ return 28
+ else:
+ return 30
+
+
+class ddmmyyyy(object):
+ """immutable dates"""
+
+ def __init__(self, date=None, fmt="ddmmyyyy", work_saturday=False):
+ self.work_saturday = work_saturday
+ if date is None:
+ return
+ try:
+ if fmt == "ddmmyyyy":
+ self.day, self.month, self.year = date.split("/")
+ elif fmt == "iso":
+ self.year, self.month, self.day = date.split("-")
+ else:
+ raise ValueError("invalid format spec. (%s)" % fmt)
+ self.year = int(self.year)
+ self.month = int(self.month)
+ self.day = int(self.day)
+ except ValueError:
+ raise ScoValueError("date invalide: %s" % date)
+ # accept years YYYY or YY, uses 1970 as pivot
+ if self.year < 1970:
+ if self.year > 100:
+ raise ScoInvalidDateError("Année invalide: %s" % self.year)
+ if self.year < 70:
+ self.year = self.year + 2000
+ else:
+ self.year = self.year + 1900
+ if self.month < 1 or self.month > 12:
+ raise ScoInvalidDateError("Mois invalide: %s" % self.month)
+
+ if self.day < 1 or self.day > MonthNbDays(self.month, self.year):
+ raise ScoInvalidDateError("Jour invalide: %s" % self.day)
+
+ # weekday in 0-6, where 0 is monday
+ self.weekday = calendar.weekday(self.year, self.month, self.day)
+
+ self.time = time.mktime((self.year, self.month, self.day, 0, 0, 0, 0, 0, 0))
+
+ def iswork(self):
+ "returns true if workable day"
+ if self.work_saturday:
+ nbdays = 6
+ else:
+ nbdays = 5
+ if (
+ self.weekday >= 0 and self.weekday < nbdays
+ ): # monday-friday or monday-saturday
+ return 1
+ else:
+ return 0
+
+ def __repr__(self):
+ return "'%02d/%02d/%04d'" % (self.day, self.month, self.year)
+
+ def __str__(self):
+ return "%02d/%02d/%04d" % (self.day, self.month, self.year)
+
+ def ISO(self):
+ "iso8601 representation of the date"
+ return "%04d-%02d-%02d" % (self.year, self.month, self.day)
+
+ def next_day(self, days=1):
+ "date for the next day (nota: may be a non workable day)"
+ day = self.day + days
+ month = self.month
+ year = self.year
+
+ while day > MonthNbDays(month, year):
+ day = day - MonthNbDays(month, year)
+ month = month + 1
+ if month > 12:
+ month = 1
+ year = year + 1
+ return self.__class__(
+ "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
+ )
+
+ def prev(self, days=1):
+ "date for previous day"
+ day = self.day - days
+ month = self.month
+ year = self.year
+ while day <= 0:
+ month = month - 1
+ if month == 0:
+ month = 12
+ year = year - 1
+ day = day + MonthNbDays(month, year)
+
+ return self.__class__(
+ "%02d/%02d/%04d" % (day, month, year), work_saturday=self.work_saturday
+ )
+
+ def next_monday(self):
+ "date of next monday"
+ return self.next_day((7 - self.weekday) % 7)
+
+ def prev_monday(self):
+ "date of last monday, but on sunday, pick next monday"
+ if self.weekday == 6:
+ return self.next_monday()
+ else:
+ return self.prev(self.weekday)
+
+ def __cmp__(self, other): # #py3 TODO à supprimer
+ """return a negative integer if self < other,
+ zero if self == other, a positive integer if self > other"""
+ return int(self.time - other.time)
+
+ def __eq__(self, other):
+ return self.time == other.time
+
+ def __ne__(self, other):
+ return self.time != other.time
+
+ def __lt__(self, other):
+ return self.time < other.time
+
+ def __le__(self, other):
+ return self.time <= other.time
+
+ def __gt__(self, other):
+ return self.time > other.time
+
+ def __ge__(self, other):
+ return self.time >= other.time
+
+ def __hash__(self):
+ "we are immutable !"
+ return hash(self.time) ^ hash(str(self))
+
+
+# d = ddmmyyyy( '21/12/99' )
+def DateRangeISO(date_beg, date_end, workable=1):
+ """returns list of dates in [date_beg,date_end]
+ workable = 1 => keeps only workable days"""
+ if not date_beg:
+ raise ScoValueError("pas de date spécifiée !")
+ if not date_end:
+ date_end = date_beg
+ r = []
+ work_saturday = is_work_saturday()
+ try:
+ cur = ddmmyyyy(date_beg, work_saturday=work_saturday)
+ end = ddmmyyyy(date_end, work_saturday=work_saturday)
+ except (AttributeError, ValueError) as e:
+ raise ScoValueError("date invalide !") from e
+ while cur <= end:
+ if (not workable) or cur.iswork():
+ r.append(cur)
+ cur = cur.next_day()
+
+ return [x.ISO() for x in r]
+
+
+def day_names():
+ """Returns week day names.
+ If work_saturday property is set, include saturday
+ """
+ if is_work_saturday():
+ return scu.DAY_NAMES[:-1]
+ else:
+ return scu.DAY_NAMES[:-2]
+
+
+def next_iso_day(date):
+ "return date after date"
+ d = ddmmyyyy(date, fmt="iso", work_saturday=is_work_saturday())
+ return d.next_day().ISO()
+
+
+def YearTable(
+ year,
+ events=[],
+ firstmonth=9,
+ lastmonth=7,
+ halfday=0,
+ dayattributes="",
+ pad_width=8,
+):
+ """Generate a calendar table
+ events = list of tuples (date, text, color, href [,halfday])
+ where date is a string in ISO format (yyyy-mm-dd)
+ halfday is boolean (true: morning, false: afternoon)
+ text = text to put in calendar (must be short, 1-5 cars) (optional)
+ if halfday, generate 2 cells per day (morning, afternoon)
+ """
+ T = [
+ '
'
+ ]
+ T.append("")
+ month = firstmonth
+ while 1:
+ T.append('')
+ T.append(MonthTableHead(month))
+ T.append(
+ MonthTableBody(
+ month,
+ year,
+ events,
+ halfday,
+ dayattributes,
+ is_work_saturday(),
+ pad_width=pad_width,
+ )
+ )
+ T.append(MonthTableTail())
+ T.append(" | ")
+ if month == lastmonth:
+ break
+ month = month + 1
+ if month > 12:
+ month = 1
+ year = year + 1
+ T.append("
")
+ return "\n".join(T)
+
+
+# ------ HTML Calendar functions (see YearTable function)
+
+# MONTH/DAY NAMES:
+
+MONTHNAMES = (
+ "Janvier",
+ "Février",
+ "Mars",
+ "Avril",
+ "Mai",
+ "Juin",
+ "Juillet",
+ "Aout",
+ "Septembre",
+ "Octobre",
+ "Novembre",
+ "Décembre",
+)
+
+MONTHNAMES_ABREV = (
+ "Jan.",
+ "Fév.",
+ "Mars",
+ "Avr.",
+ "Mai ",
+ "Juin",
+ "Juil",
+ "Aout",
+ "Sept",
+ "Oct.",
+ "Nov.",
+ "Déc.",
+)
+
+DAYNAMES = ("Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche")
+
+DAYNAMES_ABREV = ("L", "M", "M", "J", "V", "S", "D")
+
+# COLORS:
+
+WHITE = "#FFFFFF"
+GRAY1 = "#EEEEEE"
+GREEN3 = "#99CC99"
+WEEKDAYCOLOR = GRAY1
+WEEKENDCOLOR = GREEN3
+
+
+def MonthTableHead(month):
+ color = WHITE
+ return """
+ %s |
\n""" % (
+ color,
+ MONTHNAMES_ABREV[month - 1],
+ )
+
+
+def MonthTableTail():
+ return "
\n"
+
+
+def MonthTableBody(
+ month, year, events=[], halfday=0, trattributes="", work_saturday=False, pad_width=8
+):
+ firstday, nbdays = calendar.monthrange(year, month)
+ localtime = time.localtime()
+ current_weeknum = time.strftime("%U", localtime)
+ current_year = localtime[0]
+ T = []
+ # cherche date du lundi de la 1ere semaine de ce mois
+ monday = ddmmyyyy("1/%d/%d" % (month, year))
+ while monday.weekday != 0:
+ monday = monday.prev()
+
+ if work_saturday:
+ weekend = ("D",)
+ else:
+ weekend = ("S", "D")
+
+ if not halfday:
+ for d in range(1, nbdays + 1):
+ weeknum = time.strftime(
+ "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
+ )
+ day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
+ if day in weekend:
+ bgcolor = WEEKENDCOLOR
+ weekclass = "wkend"
+ attrs = ""
+ else:
+ bgcolor = WEEKDAYCOLOR
+ weekclass = "wk" + str(monday).replace("/", "_")
+ attrs = trattributes
+ color = None
+ legend = ""
+ href = ""
+ descr = ""
+ # event this day ?
+ # each event is a tuple (date, text, color, href)
+ # where date is a string in ISO format (yyyy-mm-dd)
+ for ev in events:
+ ev_year = int(ev[0][:4])
+ ev_month = int(ev[0][5:7])
+ ev_day = int(ev[0][8:10])
+ if year == ev_year and month == ev_month and ev_day == d:
+ if ev[1]:
+ legend = ev[1]
+ if ev[2]:
+ color = ev[2]
+ if ev[3]:
+ href = ev[3]
+ if len(ev) > 4 and ev[4]:
+ descr = ev[4]
+ #
+ cc = []
+ if color is not None:
+ cc.append('' % color)
+ else:
+ cc.append(' | ')
+
+ if href:
+ href = 'href="%s"' % href
+ if descr:
+ descr = 'title="%s"' % html.escape(descr, quote=True)
+ if href or descr:
+ cc.append("" % (href, descr))
+
+ if legend or d == 1:
+ if pad_width is not None:
+ n = pad_width - len(legend) # pad to 8 cars
+ if n > 0:
+ legend = (
+ " " * (n // 2) + legend + " " * ((n + 1) // 2)
+ )
+ else:
+ legend = " " # empty cell
+ cc.append(legend)
+ if href or descr:
+ cc.append("")
+ cc.append(" | ")
+ cell = "".join(cc)
+ if day == "D":
+ monday = monday.next_day(7)
+ if (
+ weeknum == current_weeknum
+ and current_year == year
+ and weekclass != "wkend"
+ ):
+ weekclass += " currentweek"
+ T.append(
+ '%d%s | %s
'
+ % (bgcolor, weekclass, attrs, d, day, cell)
+ )
+ else:
+ # Calendar with 2 cells / day
+ for d in range(1, nbdays + 1):
+ weeknum = time.strftime(
+ "%U", time.strptime("%d/%d/%d" % (d, month, year), "%d/%m/%Y")
+ )
+ day = DAYNAMES_ABREV[(firstday + d - 1) % 7]
+ if day in weekend:
+ bgcolor = WEEKENDCOLOR
+ weekclass = "wkend"
+ attrs = ""
+ else:
+ bgcolor = WEEKDAYCOLOR
+ weekclass = "wk" + str(monday).replace("/", "_")
+ attrs = trattributes
+ if (
+ weeknum == current_weeknum
+ and current_year == year
+ and weekclass != "wkend"
+ ):
+ weeknum += " currentweek"
+
+ if day == "D":
+ monday = monday.next_day(7)
+ T.append(
+ '%d%s | '
+ % (bgcolor, weekclass, attrs, d, day)
+ )
+ cc = []
+ for morning in (True, False):
+ color = None
+ legend = ""
+ href = ""
+ descr = ""
+ for ev in events:
+ ev_year = int(ev[0][:4])
+ ev_month = int(ev[0][5:7])
+ ev_day = int(ev[0][8:10])
+ if ev[4] is not None:
+ ev_half = int(ev[4])
+ else:
+ ev_half = 0
+ if (
+ year == ev_year
+ and month == ev_month
+ and ev_day == d
+ and morning == ev_half
+ ):
+ if ev[1]:
+ legend = ev[1]
+ if ev[2]:
+ color = ev[2]
+ if ev[3]:
+ href = ev[3]
+ if len(ev) > 5 and ev[5]:
+ descr = ev[5]
+ #
+ if color is not None:
+ cc.append('' % (color))
+ else:
+ cc.append(' | ')
+ if href:
+ href = 'href="%s"' % href
+ if descr:
+ descr = 'title="%s"' % html.escape(descr, quote=True)
+ if href or descr:
+ cc.append("" % (href, descr))
+ if legend or d == 1:
+ n = 3 - len(legend) # pad to 3 cars
+ if n > 0:
+ legend = (
+ " " * (n // 2) + legend + " " * ((n + 1) // 2)
+ )
+ else:
+ legend = " " # empty cell
+ cc.append(legend)
+ if href or descr:
+ cc.append("")
+ cc.append(" | \n")
+ T.append("".join(cc) + "
")
+ return "\n".join(T)
diff --git a/sco_version.py b/sco_version.py
index 2c6860862..86efcdf88 100644
--- a/sco_version.py
+++ b/sco_version.py
@@ -1,7 +1,7 @@
# -*- mode: python -*-
# -*- coding: utf-8 -*-
-SCOVERSION = "9.6.11"
+SCOVERSION = "9.6.12"
SCONAME = "ScoDoc"