Skip to content

Commit 63d0b34

Browse files
authored
Integrate the course assignment table according to researchers' preferences (#10)
1 parent 41e23a7 commit 63d0b34

17 files changed

+1925
-29
lines changed

app.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from course import course_bp
88
from config import config_bp
99
from course_preference import course_preference_bp
10-
from db import db, Configuration, Organization, User, Course, Teacher, Researcher
10+
from assignment import assignment_bp
11+
from db import db, Year, Organization, User, Course, Teacher, Researcher
1112
from decorators import *
1213
from flask import Flask, render_template, session, request
1314
from enums import *
@@ -27,10 +28,11 @@
2728
app.register_blueprint(course_bp, url_prefix="/course")
2829
app.register_blueprint(config_bp, url_prefix="/config")
2930
app.register_blueprint(course_preference_bp, url_prefix="/course_preference")
31+
app.register_blueprint(assignment_bp, url_prefix="/assignment")
3032

3133

3234
def get_configurations():
33-
return db.session.query(Configuration).order_by(Configuration.year.asc()).all()
35+
return db.session.query(Year).order_by(Year.year.asc()).all()
3436

3537

3638
def get_organization():

assignment.py

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
from decorators import login_required, check_access_level
2+
from db import db, User, Course, PreferenceAssignment, Teacher, Researcher, Organization, \
3+
ResearcherSupervisor, Role, AssignmentDraft, AssignmentPublished
4+
from flask import Blueprint, render_template, flash, current_app, url_for, request, make_response, redirect, session, \
5+
Flask, jsonify
6+
from util import get_current_year
7+
from enums import DEFAULT_MAX_LOAD
8+
9+
assignment_bp = Blueprint('assignment', __name__)
10+
11+
12+
@assignment_bp.route('/assignments', methods=['GET'])
13+
@login_required
14+
def assignments():
15+
return render_template('assignment.html')
16+
17+
18+
def serialize_model(model):
19+
"""Converts a SQLAlchemy model object into a dictionary."""
20+
return {column.name: getattr(model, column.name) for column in model.__table__.columns}
21+
22+
23+
@assignment_bp.route('/load_data', methods=['GET'])
24+
@login_required
25+
@check_access_level(Role.ADMIN)
26+
def load_data():
27+
current_year = get_current_year()
28+
29+
courses = [serialize_model(course) for course in
30+
db.session.query(Course).filter_by(year=current_year).order_by(Course.quadri).all() or []]
31+
supervisors = [serialize_model(supervisor) for supervisor in db.session.query(ResearcherSupervisor).all() or []]
32+
teachers = {teacher.id: serialize_model(teacher) for teacher in
33+
db.session.query(Teacher).filter_by(course_year=current_year).all() or []}
34+
researchers = {researcher.id: serialize_model(researcher) for researcher in
35+
db.session.query(Researcher).all() or []}
36+
preferences = {preference.id: serialize_model(preference) for preference in
37+
db.session.query(PreferenceAssignment).filter_by(course_year=current_year).all() or []}
38+
organizations = {organization.id: serialize_model(organization) for organization in
39+
db.session.query(Organization).all() or []}
40+
41+
saved_data = [serialize_model(assignment) for assignment in
42+
db.session.query(AssignmentDraft).filter_by(course_year=current_year).all()]
43+
44+
# Retrieve active users or users associated with preferences.
45+
# Join the 'Researcher' model to 'PreferenceAssignment' and check if the researcher has preferences.
46+
users = {
47+
user.id: serialize_model(user)
48+
for user in db.session.query(User)
49+
.filter(
50+
(User.active == True) | (User.id.in_(
51+
db.session.query(Researcher.user_id).join(PreferenceAssignment,
52+
Researcher.id == PreferenceAssignment.researcher_id)
53+
))
54+
).all()
55+
}
56+
57+
data = {
58+
'courses': courses,
59+
'users': users,
60+
'supervisors': supervisors,
61+
'teachers': teachers,
62+
'researchers': researchers,
63+
'preferences': preferences,
64+
'organizations': organizations,
65+
'current_year': current_year,
66+
'saved_data': saved_data,
67+
'MAX_LOAD': DEFAULT_MAX_LOAD,
68+
}
69+
70+
return jsonify(data)
71+
72+
73+
@assignment_bp.route('/publish_assignments', methods=['POST'])
74+
@login_required
75+
def publish_assignments():
76+
data = request.get_json()
77+
if not data:
78+
return jsonify({"error": "No data provided"}), 400
79+
80+
current_year = get_current_year()
81+
is_draft = data.get('isDraft')
82+
83+
try:
84+
# Clear existing assignments for the current year
85+
AssignmentDraft.query.filter_by(course_year=current_year).delete()
86+
if not is_draft:
87+
AssignmentPublished.query.filter_by(course_year=current_year).delete()
88+
db.session.commit()
89+
90+
assignments_to_add = []
91+
92+
for index, item in enumerate(data.get('data', [])):
93+
try:
94+
user_data = item.get('userData')
95+
course_data = item.get('courseData')
96+
97+
researcher_id = int(user_data.get('researcher_id'))
98+
load_q1 = int(user_data.get('load_q1'))
99+
load_q2 = int(user_data.get('load_q2'))
100+
101+
for course_id, properties in course_data.items():
102+
try:
103+
course_id = int(course_id)
104+
position = int(properties.get('position'))
105+
comment = properties.get('comment')
106+
if not isinstance(comment, (str, type(None))):
107+
raise ValueError("Invalid comment format")
108+
109+
assignments_to_add.append(AssignmentDraft(
110+
course_id=course_id, course_year=current_year, researcher_id=researcher_id,
111+
load_q1=load_q1, load_q2=load_q2, position=position, comment=comment
112+
))
113+
if not is_draft:
114+
assignments_to_add.append(AssignmentPublished(
115+
course_id=course_id, course_year=current_year, researcher_id=researcher_id,
116+
load_q1=load_q1, load_q2=load_q2, position=position, comment=comment
117+
))
118+
except (ValueError, TypeError) as e:
119+
course = db.session.query(Course).filter_by(id=course_id, year=current_year).first()
120+
return jsonify({
121+
"error": f"Error for researcher_id {researcher_id}, course_name {course.code} - {course.title} : {e}"}), 400
122+
123+
except (ValueError, TypeError) as e:
124+
return jsonify({"error": f"Invalid data : {str(e)}"}), 400
125+
126+
db.session.add_all(assignments_to_add)
127+
db.session.commit()
128+
return jsonify({"message": "Assignments published successfully"}), 200
129+
130+
except Exception as e:
131+
db.session.rollback()
132+
return jsonify({"error": f"Failed to publish assignments: {str(e)}"}), 500

auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from db import db, User, Configuration
1+
from db import db, User
22
from flask import Blueprint, current_app, url_for, request, make_response, redirect, session
33
from onelogin.saml2.auth import OneLogin_Saml2_Auth
44
from onelogin.saml2.utils import OneLogin_Saml2_Utils

config.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from decorators import login_required, check_access_level
2-
from db import db, Configuration, Role
2+
from db import db, Year, Role
33
from flask import Blueprint, render_template, flash, current_app, url_for, request, make_response, redirect, session, \
44
Flask
55
import logging
@@ -11,7 +11,7 @@
1111
@login_required
1212
@check_access_level(Role.ADMIN)
1313
def manage_years():
14-
configurations = Configuration.query.order_by(Configuration.year.desc()).all()
14+
configurations = Year.query.order_by(Year.year.desc()).all()
1515
return render_template('config.html', configurations=configurations)
1616

1717

@@ -20,17 +20,17 @@ def manage_years():
2020
@login_required
2121
@check_access_level(Role.ADMIN)
2222
def new_year():
23-
last_year = Configuration.query.order_by(Configuration.year.desc()).first()
23+
last_year = Year.query.order_by(Year.year.desc()).first()
2424
new_year = last_year.year + 1
2525

2626
try:
2727
# Check whether the following year already exists in the database
28-
existing_config = Configuration.query.filter_by(year=new_year).first()
28+
existing_config = Year.query.filter_by(year=new_year).first()
2929
if existing_config is not None:
3030
flash("The year already exists", "error")
3131
return redirect(url_for("index"))
3232

33-
config = Configuration(year=new_year)
33+
config = Year(year=new_year)
3434
db.session.add(config)
3535
db.session.commit()
3636
flash("Year created successfully", "success")
@@ -49,8 +49,8 @@ def new_year():
4949
@check_access_level(Role.ADMIN)
5050
def change_year(year):
5151
try:
52-
new_current_year = Configuration.query.filter_by(year=year).first()
53-
Configuration.update_current_year(new_current_year.id)
52+
new_current_year = Year.query.filter_by(year=year).first()
53+
Year.update_current_year(new_current_year.id)
5454

5555
except Exception as e:
5656
db.session.rollback()

course.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from decorators import login_required, check_access_level
2-
from db import db, User, Course, Teacher, Organization, Evaluation, Configuration, Role
2+
from db import db, User, Course, Teacher, Organization, Evaluation, Year, Role
33
from flask import Blueprint, render_template, flash, url_for, request, make_response, redirect, \
44
Flask, jsonify, session
55
from util import get_current_year
@@ -104,7 +104,7 @@ def add_course():
104104

105105
db.session.add(new_course)
106106
db.session.commit()
107-
return redirect(url_for("course.courses", current_year=year))
107+
return redirect(url_for("course.courses", year=year))
108108
except Exception as e:
109109
db.session.rollback()
110110
raise e
@@ -125,8 +125,8 @@ def search_teachers():
125125
if not validate_string_pattern(search_term):
126126
return make_response("Invalid search term", 400)
127127

128-
teachers = db.session.query(User).join(Teacher).filter(
129-
User.active == True,
128+
teachers = db.session.query(User).filter(
129+
User.is_teacher == True,
130130
User.name.ilike(f'%{search_term}%')
131131
).all()
132132
results = [{'id': teacher.id, 'text': f'{teacher.name} {teacher.first_name}'} for teacher in teachers]
@@ -172,7 +172,7 @@ def update_course_info():
172172
organisation_code = request.form.getlist('organization_code[]')
173173
if not len(organisation_code):
174174
flash("Please select at least one organization", "danger")
175-
return redirect(url_for('course.course_info', course_id=course_id))
175+
return redirect(url_for('course.course_info', course_id=course_id, year=year))
176176

177177
course = db.session.query(Course).filter(Course.id == course_id, Course.year == year).first()
178178
if not course:

course_preference.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@
99
course_preference_bp = Blueprint('course_preference', __name__)
1010

1111

12-
def delete_old_preferences(researcher_id, course_ids, current_year):
12+
def delete_old_preferences(researcher_id, current_year):
1313
try:
1414
db.session.query(PreferenceAssignment).filter(
1515
PreferenceAssignment.researcher_id == researcher_id,
1616
PreferenceAssignment.course_year == current_year,
17-
PreferenceAssignment.course_id.in_(course_ids)
1817
).delete()
1918
db.session.commit()
2019
except Exception as e:
@@ -41,14 +40,15 @@ def save_preference():
4140
if researcher is None:
4241
return make_response("User is not a researcher", 500)
4342

44-
new_course_ids = {preference['course_id'] for preference in preferences}
45-
delete_old_preferences(researcher.id, new_course_ids, current_year)
43+
delete_old_preferences(researcher.id, current_year)
4644

45+
rank = 0
4746
for preference in preferences:
4847
try:
48+
rank += 1
4949
course_id = preference['course_id']
5050
course_year = preference['course_year']
51-
new_preference = PreferenceAssignment(course_id=course_id, course_year=course_year,
51+
new_preference = PreferenceAssignment(rank=rank, course_id=course_id, course_year=course_year,
5252
researcher_id=researcher.id)
5353
db.session.add(new_preference)
5454
db.session.commit()

create_db.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import argparse
22
from app import app, db
33
from datetime import datetime
4-
from db import User, Configuration, Organization
4+
from db import User, Year, Organization
55

66

77
def create_database(name, first_name, email):
@@ -27,10 +27,10 @@ def add_first_admin(name, first_name, email):
2727
# Add the first year corresponding to the current year
2828
def initialize_configuration():
2929
current_year = datetime.now().year
30-
existing_year = Configuration.query.filter_by(year=current_year).first()
30+
existing_year = Year.query.filter_by(year=current_year).first()
3131

3232
if existing_year is None:
33-
config = Configuration(year=current_year, is_current_year=True)
33+
config = Year(year=current_year, is_current_year=True)
3434
db.session.add(config)
3535
db.session.commit()
3636

db.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class Teacher(db.Model):
110110
class PreferenceAssignment(db.Model):
111111
__tablename__ = 'preference_assignment'
112112
id = db.Column(db.Integer, primary_key=True)
113+
rank = db.Column(db.Integer, nullable=False)
113114
course_id = db.Column(db.Integer, nullable=False)
114115
course_year = db.Column(db.Integer, nullable=False)
115116
researcher_id = db.Column(db.Integer, db.ForeignKey('researcher.id'), nullable=False)
@@ -127,18 +128,18 @@ class PreferenceAssignment(db.Model):
127128
researcher = db.relationship('Researcher', backref=db.backref('researcher_preference_assignment', lazy=True))
128129

129130

130-
class Configuration(db.Model):
131-
__tablename__ = 'configuration'
131+
class Year(db.Model):
132+
__tablename__ = 'year'
132133
id = db.Column(db.Integer, primary_key=True)
133134
year = db.Column(db.Integer, nullable=False)
134135
is_current_year = db.Column(db.Boolean, default=False)
135136

136137
@classmethod
137-
def update_current_year(cls, config_id):
138+
def update_current_year(cls, year_id):
138139
# Update all years to set is_current_year to False
139140
cls.query.update({cls.is_current_year: False})
140141

141-
selected_config = cls.query.get(config_id)
142+
selected_config = cls.query.get(year_id)
142143
if selected_config:
143144
selected_config.is_current_year = True
144145
db.session.commit()
@@ -172,6 +173,44 @@ class CourseOrganization(db.Model):
172173
)
173174

174175

176+
class AssignmentDraft(db.Model):
177+
__tablename__ = 'assignment_draft'
178+
id = db.Column(db.Integer, primary_key=True)
179+
course_id = db.Column(db.Integer, nullable=False)
180+
course_year = db.Column(db.Integer, nullable=False)
181+
researcher_id = db.Column(db.Integer, db.ForeignKey('researcher.id'))
182+
load_q1 = db.Column(db.Integer)
183+
load_q2 = db.Column(db.Integer)
184+
position = db.Column(db.Integer)
185+
comment = db.Column(db.String(500))
186+
187+
__table_args__ = (
188+
db.ForeignKeyConstraint(
189+
['course_id', 'course_year'],
190+
['course.id', 'course.year']
191+
),
192+
)
193+
194+
195+
class AssignmentPublished(db.Model):
196+
__tablename__ = 'assignment_published'
197+
id = db.Column(db.Integer, primary_key=True)
198+
course_id = db.Column(db.Integer, nullable=False)
199+
course_year = db.Column(db.Integer, nullable=False)
200+
researcher_id = db.Column(db.Integer, db.ForeignKey('researcher.id'))
201+
load_q1 = db.Column(db.Integer)
202+
load_q2 = db.Column(db.Integer)
203+
position = db.Column(db.Integer)
204+
comment = db.Column(db.String(500))
205+
206+
__table_args__ = (
207+
db.ForeignKeyConstraint(
208+
['course_id', 'course_year'],
209+
['course.id', 'course.year']
210+
),
211+
)
212+
213+
175214
class Evaluation(db.Model):
176215
__tablename__ = 'evaluation'
177216

0 commit comments

Comments
 (0)