diff --git a/app.py b/app.py index 0b5923d..aed3b8d 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,8 @@ import datetime import jinja2.defaults +from sqlalchemy import and_ +from sqlalchemy.orm import joinedload from auth import auth_bp from user import user_bp @@ -8,7 +10,7 @@ from config import config_bp from course_preference import course_preference_bp from assignment import assignment_bp -from db import db, Year, Organization, User, Course, Teacher, Researcher, Evaluation +from db import db, Year, Organization, User, Course, Teacher, Researcher, Evaluation, AssignmentPublished from decorators import * from flask import Flask, render_template, session, request from enums import * @@ -53,11 +55,51 @@ def inject_configurations(): # Routes @app.route('/') @login_required -def index(): # put application's code here +def index(): current_year = get_current_year() + year = db.session.query(Year).filter_by(year=current_year).first() user = db.session.query(User).filter_by(email=session['email']).first() - courses_teacher = db.session.query(Course).filter_by(year=current_year).join(Teacher).filter(Teacher.user_id == user.id).all() - return render_template("home.html", user=user, courses=courses_teacher, evaluations=user.evaluations) + teacher = db.session.query(Teacher).filter_by(user_id=user.id).first() if user.is_teacher else None + + data = {} + + if user.is_teacher: + # Populate teacher-specific data + teacher_courses = db.session.query(Course).join(Teacher).filter( + and_(Teacher.user_id == user.id, Course.year == current_year) + ).all() + + supervised_researchers = [] + for researcher_supervisor in teacher.user.researchers: + researcher = researcher_supervisor.researcher + current_year_courses = [ + course for course in researcher.assigned_courses if course.year == current_year + ] + researcher.current_assigned_courses = current_year_courses + supervised_researchers.append(researcher) + + data.update({ + "teacher_courses": teacher_courses, + "supervised_researchers": supervised_researchers + }) + + # To change if we decide to use session to store roles + elif is_researcher(): + researcher = db.session.query(Researcher).filter_by(user_id=user.id).first() + # Populate researcher-specific data + researcher_courses = researcher.assigned_courses + researcher_current_courses = [ + course for course in researcher_courses if course.year == current_year + ] + evaluations = researcher.user.evaluations + + data.update({ + "researcher_courses": researcher_courses, + "researcher_current_courses": researcher_current_courses, + "researcher_evaluations": evaluations + }) + + return render_template("home.html", user=user, data=data) if __name__ == '__main__': diff --git a/assignment.py b/assignment.py index 3a0c48b..082e770 100644 --- a/assignment.py +++ b/assignment.py @@ -1,6 +1,6 @@ from decorators import login_required, check_access_level from db import db, User, Course, PreferenceAssignment, Teacher, Researcher, Organization, \ - ResearcherSupervisor, Role, AssignmentDraft, AssignmentPublished + ResearcherSupervisor, Role, AssignmentDraft, AssignmentPublished, Year from flask import Blueprint, render_template, flash, current_app, url_for, request, make_response, redirect, session, \ Flask, jsonify from util import get_current_year @@ -79,6 +79,7 @@ def publish_assignments(): current_year = get_current_year() is_draft = data.get('isDraft') + is_teacher_publication = data.get('isTeacherPublication') try: # Clear existing assignments for the current year @@ -113,8 +114,7 @@ def publish_assignments(): if not is_draft: assignments_to_add.append(AssignmentPublished( course_id=course_id, course_year=current_year, researcher_id=researcher_id, - load_q1=load_q1, load_q2=load_q2, position=position, comment=comment - )) + load_q1=load_q1, load_q2=load_q2, position=position, comment=comment)) except (ValueError, TypeError) as e: course = db.session.query(Course).filter_by(id=course_id, year=current_year).first() return jsonify({ diff --git a/course.py b/course.py index cac8d77..021a3a3 100644 --- a/course.py +++ b/course.py @@ -1,7 +1,7 @@ from sqlalchemy import func from decorators import login_required, check_access_level -from db import db, User, Course, Teacher, Organization, Evaluation, Year, Role +from db import db, User, Course, Teacher, Organization, Evaluation, Year, Role, Researcher, AssignmentPublished from flask import Blueprint, render_template, flash, url_for, request, make_response, redirect, \ Flask, jsonify, session from util import get_current_year @@ -97,7 +97,8 @@ def add_course(): is_course = db.session.query(Course).filter(Course.code == code, Course.year == year).first() if is_course is not None: - return make_response("Course already exists", 500) + flash("Course already exists", "danger") + return redirect(url_for('course.add_course')) new_course = Course(year=year, code=code, title=title, quadri=quadri, language=language) # Fetch organizations and add them to the course @@ -106,7 +107,7 @@ def add_course(): db.session.add(new_course) db.session.commit() - return redirect(url_for("course.courses", year=year)) + return redirect(url_for("course.course_info", course_id=new_course.id, year=year)) except Exception as e: db.session.rollback() raise e @@ -117,7 +118,7 @@ def add_course(): @check_access_level(Role.ADMIN) def courses(year): courses = db.session.query(Course).filter_by(year=year).all() - return render_template('courses.html', courses=courses, current_year=year) + return render_template('courses.html', courses=courses, year=year) @course_bp.route('/search_teachers') @@ -141,16 +142,23 @@ def search_teachers(): @check_access_level(Role.ADMIN) def course_info(course_id, year): course = db.session.query(Course).filter(Course.id == course_id, Course.year == year).first() + if not course: return make_response("Course not found", 404) all_years = db.session.query(Course).filter_by(id=course.id).distinct(Course.year).order_by( Course.year.desc()).all() + query_all_assistants = db.session.query(User, Course.year).join(Researcher).join( + AssignmentPublished).join(Course).filter( + AssignmentPublished.course_id == course_id + ).order_by(Course.year.desc()).all() + all_assistants = [{"user": user, "year": year} for user, year in query_all_assistants] + evaluations = db.session.query(Evaluation).filter_by(course_id=course_id).all() or [] return render_template('course_info.html', course=course, all_years=all_years, current_year=year, - evaluations=evaluations) + evaluations=evaluations, all_assistants=all_assistants) @course_bp.route('/update_course_info', methods=['POST']) @@ -276,7 +284,7 @@ def course_evaluation(evaluation_id): course = evaluation.course user = db.session.query(User).filter_by(id=session['user_id']).first() - teachers = course.course_teacher + teachers = course.teachers # Accessible only to the admin, the evaluation creator and the course teachers if (not user.is_admin) and (user.id != evaluation.user_id) and (user.id not in [teacher.user_id for teacher in teachers]): diff --git a/db.py b/db.py index bdff5ec..b877c16 100644 --- a/db.py +++ b/db.py @@ -2,6 +2,7 @@ from flask import Flask from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import Enum import json from sqlalchemy.orm import validates @@ -60,6 +61,7 @@ class Course(db.Model): __table_args__ = (db.UniqueConstraint('id', 'year', name='uq_course_id_year'),) + teachers = db.relationship('Teacher', backref=db.backref('course_teacher', lazy=True)) organizations = db.relationship('Organization', secondary='course_organization', back_populates='courses', @@ -67,6 +69,15 @@ class Course(db.Model): "CourseOrganization.course_year)", secondaryjoin="Organization.id == CourseOrganization.organization_id") + assistants = db.relationship( + 'User', + secondary='assignment_published', + primaryjoin="and_(Course.id == AssignmentPublished.course_id, Course.year == AssignmentPublished.course_year)", + secondaryjoin="and_(Researcher.id == AssignmentPublished.researcher_id, User.id == Researcher.user_id)", + backref='assigned_courses', + viewonly=True + ) + class Researcher(db.Model): __tablename__ = 'researcher' @@ -77,6 +88,8 @@ class Researcher(db.Model): researcher_type = db.Column(db.String(30)) user = db.relationship('User', backref=db.backref('researcher_profile', uselist=False)) + assigned_courses = db.relationship('Course', secondary='assignment_published', + backref=db.backref('courses', lazy=True), order_by='Course.year.desc()') class ResearcherSupervisor(db.Model): @@ -104,11 +117,11 @@ class Teacher(db.Model): ) user = db.relationship('User', backref=db.backref('user_teacher', uselist=False)) - course = db.relationship('Course', backref=db.backref('course_teacher', lazy=True)) class PreferenceAssignment(db.Model): __tablename__ = 'preference_assignment' + rank = db.Column(db.Integer, nullable=False) id = db.Column(db.Integer, primary_key=True) rank = db.Column(db.Integer, nullable=False) course_id = db.Column(db.Integer, nullable=False) diff --git a/enums.py b/enums.py index 9a2d9a1..ecfe366 100644 --- a/enums.py +++ b/enums.py @@ -13,4 +13,4 @@ TASK = ["Exercise sessions", "Project(s)", "Supervisor of monitoring students", "Q&A sessions/restructuring", "Corrections/jury/oral", "Creation of new content (new exercises, new project statement, ...)"] EVALUATION_HOUR = ["< 2h", "2h - 4h", "4h - 6h", "6h - 8h", "> 8h"] -WORKLOAD = ["Very light", "Light", "Reasonable", "High", "Very high"] +WORKLOAD = ["Very light", "Light", "Reasonable", "High", "Very high"] \ No newline at end of file diff --git a/static/scripts/assignment_table.js b/static/scripts/assignment_table.js index f89fdc5..90dc8e9 100644 --- a/static/scripts/assignment_table.js +++ b/static/scripts/assignment_table.js @@ -168,7 +168,6 @@ fetch('/assignment/load_data') const columns = getCourseColumns(); let data = fixedRows.concat(userRows); - const nbrLines = data.length - 1; const nbrCols = columns.length - 1; @@ -203,7 +202,23 @@ fetch('/assignment/load_data') contextMenu: ['commentsAddEdit', 'commentsRemove', 'hidden_columns_hide', 'hidden_rows_hide', 'hidden_columns_show', 'hidden_rows_show'], comments: true, filters: true, - dropdownMenu: ['filter_by_value', 'filter_action_bar', 'undo'], + dropdownMenu: { + items: { + 'filter_by_value': {}, + 'filter_action_bar': {}, + '---------': {}, + 'clear_all_filters': { + name: 'Reset filters', + callback: function () { + const filtersPlugin = this.getPlugin('filters'); + if (filtersPlugin) { + filtersPlugin.clearConditions(); + filtersPlugin.filter(); + } + }, + }, + }, + }, className: 'controlsQuickFilter htCenter htMiddle', colHeaders: allHeaders, columns: columns, @@ -336,32 +351,29 @@ fetch('/assignment/load_data') ], beforeFilter(conditionsStack) { const filtersPlugin = this.getPlugin('filters'); - //Get the user data without the fixed rows + // Get the user data without the fixed rows const tab = this.getData().slice(lenFixedRowsText); - let values = []; - const filteredResults = []; - //Get the number of the column to filter - if (conditionsStack && conditionsStack.length > 0) { - const col = conditionsStack[0].column; - - if (conditionsStack && conditionsStack.length > 0) { - for (let i = 0; i < conditionsStack.length; i++) { - //Get the matching values to filter - values = conditionsStack[i].conditions[0].args.flat(); - - //Verify if the row value for the specific column is in the filter - for (const row of tab) { - if (values.includes(row[col])) { - //Push id to the filteredResults array - filteredResults.push(row[0]); - } - } - } + // Start with all row IDs as potentially valid + let filteredResults = tab.map(row => row[0]); // Array of IDs + + // Process each condition and refine the filteredResults + for (const condition of conditionsStack) { + const col = condition.column; // Column to filter + const values = condition.conditions.flatMap(c => c.args).flat(); // Values to match + + // Filter rows that match the current condition + filteredResults = filteredResults.filter(id => + tab.some(row => row[0] === id && values.includes(row[col])) + ); + + // Exit early if no rows match any condition + if (filteredResults.length === 0) break; } + + // Apply the final filtered IDs to the Handsontable filter filtersPlugin.clearConditions(); - //Create a new condition to filter the data based on the id filtersPlugin.addCondition(0, 'by_value', [filteredResults]); } }, @@ -398,7 +410,7 @@ fetch('/assignment/load_data') toastNotification.show(); }); - async function saveAssignment(isDraft = false) { + async function saveAssignment(isDraft = false, isTeacherPublication = false) { const slicedData = data.slice(lenFixedRowsText); const savedData = []; const commentsPlugin = table.getPlugin('comments'); @@ -436,7 +448,8 @@ fetch('/assignment/load_data') const tableData = { data: savedData, - isDraft: isDraft + isDraft: isDraft, + isTeacherPublication: isTeacherPublication, }; try { @@ -479,7 +492,11 @@ fetch('/assignment/load_data') toastNotification.show(); }); - $('#button-publish-assignments').click(async function () { + $('#button-publish-teachers').click(async function () { + await saveAssignment(false, true); + }); + + $('#button-publish-everyone').click(async function () { await saveAssignment(); }); }); diff --git a/templates/assignment.html b/templates/assignment.html index ba2f264..06e33f4 100644 --- a/templates/assignment.html +++ b/templates/assignment.html @@ -100,9 +100,18 @@

- +
+
+ +
+
+ +
+
diff --git a/templates/course_info.html b/templates/course_info.html index 1fd9728..28a383a 100644 --- a/templates/course_info.html +++ b/templates/course_info.html @@ -32,14 +32,17 @@

Assistant(s) for this year ({{ course.year }} - {{ course - - + - - - + {% for assistant in course.assistants %} + + + + {% endfor %}
Last nameFirst nameResearcher
No assistants assigned this year.
+ {{ assistant.name }} {{ assistant.first_name }} +
@@ -72,9 +75,17 @@

History

Last name First name + Year + {% for assistant in all_assistants %} + + {{ assistant.user.name }} + {{ assistant.user.first_name }} + {{ assistant.year }} + + {% endfor %}
diff --git a/templates/course_info_template.html b/templates/course_info_template.html index 1ac8ab7..40f4536 100644 --- a/templates/course_info_template.html +++ b/templates/course_info_template.html @@ -64,7 +64,7 @@ - {% for t in course.course_teacher %} + {% for t in course.teachers %} {% endfor %} diff --git a/templates/courses.html b/templates/courses.html index f574819..bf86092 100644 --- a/templates/courses.html +++ b/templates/courses.html @@ -38,7 +38,7 @@

Courses