commit 361bae10acac37565fe47b20e63d134e9e402675 Author: Nekori Date: Thu May 6 10:26:28 2021 +0200 first commit diff --git a/app.db b/app.db new file mode 100644 index 0000000..deea546 Binary files /dev/null and b/app.db differ diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..0d7bc57 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,11 @@ +from flask import Flask +from config import Config +from flask_sqlalchemy import SQLAlchemy +from flask_migrate import Migrate + +app = Flask(__name__) +app.config.from_object(Config) +db = SQLAlchemy(app) +migrate = Migrate(app, db) + +from app import routes, models \ No newline at end of file diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..f607837 --- /dev/null +++ b/app/forms.py @@ -0,0 +1,97 @@ +from flask_wtf import FlaskForm +from flask_wtf.file import FileAllowed +from wtforms import StringField, SubmitField, FileField, TextAreaField, RadioField +from wtforms.validators import DataRequired, NumberRange, Length, Regexp + +import yaml +import os + +REPERTOIRE_YAML = "./export/" + +if not os.path.exists(REPERTOIRE_YAML): + os.makedirs(REPERTOIRE_YAML) + +class Form(FlaskForm): + + exporter = SubmitField("Exporter") + fichier = FileField("Choisir fichier", validators=[FileAllowed(["yml"], "Fichier Yaml seulement!")]) + importer = SubmitField("Importer") + +class PNForm(Form): + file_length = len("PN0") + + code = StringField("Code", validators=[DataRequired(), Length(3,3)]) + nom = StringField("Nom", validators=[DataRequired()] ) + diminutif = StringField("Diminutif", validators=[DataRequired()] ) + description = TextAreaField("Description", validators=[DataRequired()] ) + type = RadioField("Type", choices=[1,2,3], validators=[DataRequired()]) + +class ACForm(Form): + file_length = len("AC0000.yml") + + code = StringField("Code", validators=[DataRequired(), Length(6,6)]) + + +class SAEForm(Form): + file_length = len("SAE00.yml") + + code = StringField("Code", validators=[DataRequired(), Length(5,5)]) + titre = StringField("Titre", validators=[DataRequired()] ) + semestre = StringField("Semestre", validators=[DataRequired()] ) + heures_encadrees = StringField("Heures encadrées", validators=[DataRequired()] ) + heures_tp = StringField("Heures TP", validators=[DataRequired()] ) + projet = StringField("Projet", validators=[DataRequired()] ) + description = TextAreaField("Description", validators=[DataRequired()] ) + coef = StringField("Coef.", validators=[DataRequired()] ) + acs = StringField("ACs", validators=[DataRequired()] ) + ressources = StringField("Ressources", validators=[DataRequired()] ) + livrables = TextAreaField("Livrables", validators=[DataRequired()] ) + motscles = StringField("Mots clés", validators=[DataRequired()] ) + + +class RessourceForm(Form): + file_length = len("R000.yml") + + code = StringField("Code", validators=[DataRequired(), Length(4,4)]) + nom = StringField("Nom", validators=[DataRequired()] ) + semestre = StringField("Semestre", validators=[DataRequired()] ) + heures_formation = StringField("Heures formation", validators=[DataRequired()] ) + heures_tp = StringField("Heures TP", validators=[DataRequired()] ) + coef = StringField("Coef.", validators=[DataRequired()] ) + acs = StringField("ACs", validators=[DataRequired()] ) + saes = StringField("SAEs", validators=[DataRequired()] ) + prerequis = StringField("Prérequis", validators=[DataRequired()] ) + contexte = TextAreaField("Contexte", validators=[DataRequired()] ) + contenu = TextAreaField("Contenu", validators=[DataRequired()] ) + motscles = StringField("Mots clés", validators=[DataRequired()] ) + +class CompetenceForm(Form): + file_length = len("RT0.yml") + + code = StringField("Code", validators=[DataRequired(), Length(3,3)]) + nom = StringField("Nom", validators=[DataRequired()] ) + diminutif = StringField("Diminutif", validators=[DataRequired()] ) + description = TextAreaField("Description", validators=[DataRequired()] ) + composantes = TextAreaField("Composantes", validators=[DataRequired()] ) + situations = TextAreaField("Situations", validators=[DataRequired()] ) + niveaux = TextAreaField("Niveaux", validators=[DataRequired()] ) + +def form_import(form): + """ Si import a été appuyé et qu'il n'y a pas d'erreur d'import => importe le fichier yaml""" + if form.importer.data and len(form.fichier.errors) == 0 and len(form.fichier.data.filename) == form.file_length: + fichier_Yaml = yaml.safe_load(form.fichier.data.read()) + for categorie, valeur in fichier_Yaml.items(): + form[categorie].data = valeur + form.validate_on_submit() # Réinitialise les messages d'erreur + return form + +def form_export(form): + """ Si le formulaire est valide => exporte dans un fichier yaml avec les informations du formulaire """ + output = {} + + for categorie, valeur in list(form.data.items())[3:-1]: + output[categorie] = valeur + + fichier = REPERTOIRE_YAML + form.code.data + ".yml" + with open(fichier, "w", encoding="utf8") as fid: + fid.write(yaml.dump(output)) \ No newline at end of file diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..c65bb73 --- /dev/null +++ b/app/models.py @@ -0,0 +1,11 @@ +from app import db + +class PN(db.Model): + code = db.Column(db.String(3), primary_key = True) + nom = db.Column(db.String(255)) + diminutif = db.Column(db.String(30)) + description = db.Column(db.Text()) + type = db.Column(db.Integer()) + + def __repr__(self): + return "".format(self.code) \ No newline at end of file diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 0000000..4217c48 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,59 @@ +from flask import render_template, flash, redirect, url_for, request +from app import app +from app.forms import * +from app.models import PN + +import yaml + +@app.route("/") +@app.route("/index") +def index(): + return render_template("base.html") + +@app.route("/PN", methods=["GET","POST"]) +def PN(): + form = PNForm() + form_validation = form.validate_on_submit() + form = form_import(form) + if form_validation and form.exporter.data: + flash("Ajout du référentiel PN: {} ".format(form.code.data)) + form_export(form) + return redirect(url_for("PN")) + return render_template("PN.html", form = form) + +@app.route("/AC", methods=["GET","POST"]) +def AC(): + return render_template("PN.html", form = form) + +@app.route("/SAE", methods=["GET","POST"]) +def SAE(): + form = SAEForm() + form_validation = form.validate_on_submit() + form = form_import(form) + if form_validation and form.exporter.data: + flash("Ajout du référentiel SAE: {} ".format(form.code.data)) + form_export(form) + return redirect(url_for("SAE")) + return render_template("SAE.html", form = form) + +@app.route("/Ressource", methods=["GET","POST"]) +def Ressource(): + form = RessourceForm() + form_validation = form.validate_on_submit() + form = form_import(form) + if form_validation and form.exporter.data: + flash("Ajout du référentiel Ressource: {} ".format(form.code.data)) + form_export(form) + return redirect(url_for("Ressource")) + return render_template("Ressource.html", form = form) + +@app.route("/Competence", methods=["GET","POST"]) +def Competence(): + form = CompetenceForm() + form_validation = form.validate_on_submit() + form = form_import(form) + if form_validation and form.exporter.data: + flash("Ajout du référentielCompetence: {} ".format(form.code.data)) + form_export(form) + return redirect(url_for("Competence")) + return render_template("Competence.html", form = form) \ No newline at end of file diff --git a/app/templates/Competence.html b/app/templates/Competence.html new file mode 100644 index 0000000..73f8042 --- /dev/null +++ b/app/templates/Competence.html @@ -0,0 +1,16 @@ +{% extends "form.html" %} + +{% block title %}RT Form{% endblock %} +{% block form_title %}Formulaire de Compétences (RT){% endblock %} + +{% block formulaire %} + +{{ render_field(form.code,"input") }} +{{ render_field(form.nom,"input") }} +{{ render_field(form.diminutif,"input") }} +{{ render_field(form.description,"textarea") }} +{{ render_field(form.composantes,"textarea") }} +{{ render_field(form.situations,"textarea") }} +{{ render_field(form.niveaux,"textarea") }} + +{% endblock %} diff --git a/app/templates/PN.html b/app/templates/PN.html new file mode 100644 index 0000000..a55478c --- /dev/null +++ b/app/templates/PN.html @@ -0,0 +1,28 @@ +{% extends "form.html" %} + +{% block title %}PN Form{% endblock %} +{% block form_title %}Formulaire de Programmes Nationaux (PN){% endblock %} + +{% block formulaire %} + +{{ render_field(form.code,"input") }} +{{ render_field(form.nom,"input") }} +{{ render_field(form.diminutif,"input") }} +{{ render_field(form.description,"textarea") }} + +
+ +
+ {% for num in form.type %} + + {% endfor %} +
+ {% for error in form.type.errors %} +

{{error}}

+ {% endfor %} +
+ +{% endblock %} diff --git a/app/templates/Ressource.html b/app/templates/Ressource.html new file mode 100644 index 0000000..3db47e8 --- /dev/null +++ b/app/templates/Ressource.html @@ -0,0 +1,21 @@ +{% extends "form.html" %} + +{% block title %}Ressource Form{% endblock %} +{% block form_title %}Formulaire de Ressources (R){% endblock %} + +{% block formulaire %} + +{{ render_field(form.code,"input") }} +{{ render_field(form.nom,"input") }} +{{ render_field(form.semestre,"input") }} +{{ render_field(form.heures_formation,"input") }} +{{ render_field(form.heures_tp,"input") }} +{{ render_field(form.coef,"input") }} +{{ render_field(form.acs,"input") }} +{{ render_field(form.saes,"input") }} +{{ render_field(form.prerequis,"input") }} +{{ render_field(form.contexte,"textarea") }} +{{ render_field(form.contenu,"textarea") }} +{{ render_field(form.motscles,"input") }} + +{% endblock %} \ No newline at end of file diff --git a/app/templates/SAE.html b/app/templates/SAE.html new file mode 100644 index 0000000..eff6845 --- /dev/null +++ b/app/templates/SAE.html @@ -0,0 +1,21 @@ +{% extends "form.html" %} + +{% block title %}SAE Form{% endblock %} +{% block form_title %}Formulaire de Situations d'apprentissages et d'évaluations (SAE){% endblock %} + +{% block formulaire %} + +{{ render_field(form.code,"input") }} +{{ render_field(form.titre,"input") }} +{{ render_field(form.semestre,"input") }} +{{ render_field(form.heures_encadrees,"input") }} +{{ render_field(form.heures_tp,"input") }} +{{ render_field(form.projet,"input") }} +{{ render_field(form.description,"textarea") }} +{{ render_field(form.coef,"input") }} +{{ render_field(form.acs,"input") }} +{{ render_field(form.ressources,"input") }} +{{ render_field(form.livrables,"textarea") }} +{{ render_field(form.motscles,"input") }} + +{% endblock %} \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..378085b --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,54 @@ + + + + + + {% block title %}{% endblock %} + + + + {% block head %}{% endblock %} + + + + + +
+
+ {% with messages = get_flashed_messages() %} + {% if messages %} + + {% endif %} + {% endwith %} + {% block content %} + {% endblock %} +
+
+ + \ No newline at end of file diff --git a/app/templates/form.html b/app/templates/form.html new file mode 100644 index 0000000..a069ad6 --- /dev/null +++ b/app/templates/form.html @@ -0,0 +1,48 @@ +{% macro render_field(field,class) %} +
+ +
+ {{ field(class=class)}} +
+ {% for error in field.errors %} +

{{error}}

+ {% endfor %} +
+{% endmacro %} +{% extends "base.html" %} + +{% block content %} +
+{{ form.hidden_tag() }} +
+

{% block form_title %}{% endblock %}

+ + {% block formulaire %}{% endblock %} + +
+
+ {{ form.exporter(class="button")}} +
+
+
+ +
+ {% for error in form.fichier.errors %} +

{{error}}

+ {% endfor %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..68846f9 --- /dev/null +++ b/config.py @@ -0,0 +1,7 @@ +import os +basedir = os.path.abspath(os.path.dirname(__file__)) + +class Config(object): + SECRET_KEY = os.environ.get("SECRET_KEY") or 'unStringRandomDuneLongueurRandom' + SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or "sqlite:///" + os.path.join(basedir, "app.db") + SQLALCHEMY_TRACK_MODIFICATIONS = False \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..07c2cfe --- /dev/null +++ b/main.py @@ -0,0 +1,7 @@ +from app import app, db +from app.models import PN + +@app.shell_context_processor +def make_shell_context(): + return {"db": db, "PN": PN} + diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..42438a5 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,90 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option( + 'sqlalchemy.url', + str(current_app.extensions['migrate'].db.engine.url).replace('%', '%%')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = current_app.extensions['migrate'].db.engine + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/be67c6934c05_pn_table.py b/migrations/versions/be67c6934c05_pn_table.py new file mode 100644 index 0000000..88ae7b1 --- /dev/null +++ b/migrations/versions/be67c6934c05_pn_table.py @@ -0,0 +1,34 @@ +"""PN table + +Revision ID: be67c6934c05 +Revises: +Create Date: 2021-05-03 17:01:39.845539 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'be67c6934c05' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('PN', + sa.Column('code', sa.String(length=3), nullable=False), + sa.Column('nom', sa.String(length=255), nullable=True), + sa.Column('diminutif', sa.String(length=30), nullable=True), + sa.Column('description', sa.Text(), nullable=True), + sa.PrimaryKeyConstraint('code') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('PN') + # ### end Alembic commands ### diff --git a/migrations/versions/c1377d41bf27_pn_table.py b/migrations/versions/c1377d41bf27_pn_table.py new file mode 100644 index 0000000..448081a --- /dev/null +++ b/migrations/versions/c1377d41bf27_pn_table.py @@ -0,0 +1,28 @@ +"""PN table + +Revision ID: c1377d41bf27 +Revises: be67c6934c05 +Create Date: 2021-05-03 17:20:44.055359 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c1377d41bf27' +down_revision = 'be67c6934c05' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('PN', sa.Column('type', sa.Integer(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('PN', 'type') + # ### end Alembic commands ###