diff --git a/conditional/__init__.py b/conditional/__init__.py index 8047cfed..8fd65cf7 100644 --- a/conditional/__init__.py +++ b/conditional/__init__.py @@ -1,5 +1,6 @@ import os import subprocess +from datetime import datetime from flask import Flask, redirect, request, render_template, g from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate @@ -16,23 +17,68 @@ app.config.from_pyfile(config) app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False -try: - app.config["GIT_REVISION"] = fetch_git_sha(app.config["ROOT_DIR"])[:7] -except (InvalidGitRepository, KeyError): - app.config["GIT_REVISION"] = "unknown" +app.config["GIT_REVISION"] = subprocess.check_output(['git', + 'rev-parse', + '--short', + 'HEAD']).decode('utf-8').rstrip() + db = SQLAlchemy(app) migrate = Migrate(app, db) -logger = structlog.get_logger() sentry = Sentry(app) ldap = CSHLDAP(app.config['LDAP_BIND_DN'], app.config['LDAP_BIND_PW'], ro=app.config['LDAP_RO']) +def start_of_year(): + start = datetime(datetime.today().year, 6, 1) + if datetime.today() < start: + start = datetime(datetime.today().year-1, 6, 1) + return start + # pylint: disable=C0413 +from conditional.models.models import UserLog + +# Configure Logging +def request_processor(logger, log_method, event_dict): # pylint: disable=unused-argument, redefined-outer-name + if 'request' in event_dict: + flask_request = event_dict['request'] + event_dict['user'] = flask_request.headers.get("x-webauth-user") + event_dict['ip'] = flask_request.remote_addr + event_dict['method'] = flask_request.method + event_dict['blueprint'] = flask_request.blueprint + event_dict['path'] = flask_request.full_path + return event_dict + + +def database_processor(logger, log_method, event_dict): # pylint: disable=unused-argument, redefined-outer-name + if 'request' in event_dict: + if event_dict['method'] != 'GET': + log = UserLog( + ipaddr=event_dict['ip'], + user=event_dict['user'], + method=event_dict['method'], + blueprint=event_dict['blueprint'], + path=event_dict['path'], + description=event_dict['event'] + ) + db.session.add(log) + db.session.flush() + db.session.commit() + del event_dict['request'] + return event_dict + +structlog.configure(processors=[ + request_processor, + database_processor, + structlog.processors.KeyValueRenderer() + ]) + +logger = structlog.get_logger() + -from conditional.blueprints.dashboard import dashboard_bp +from conditional.blueprints.dashboard import dashboard_bp # pylint: disable=ungrouped-imports from conditional.blueprints.attendance import attendance_bp from conditional.blueprints.major_project_submission import major_project_bp from conditional.blueprints.intro_evals import intro_evals_bp @@ -43,6 +89,8 @@ from conditional.blueprints.member_management import member_management_bp from conditional.blueprints.slideshow import slideshow_bp from conditional.blueprints.cache_management import cache_bp +from conditional.blueprints.co_op import co_op_bp +from conditional.blueprints.logs import log_bp app.register_blueprint(dashboard_bp) app.register_blueprint(attendance_bp) @@ -55,6 +103,8 @@ app.register_blueprint(member_management_bp) app.register_blueprint(slideshow_bp) app.register_blueprint(cache_bp) +app.register_blueprint(co_op_bp) +app.register_blueprint(log_bp) from conditional.util.ldap import ldap_get_member diff --git a/conditional/blueprints/attendance.py b/conditional/blueprints/attendance.py index 2a0e149e..78a20e62 100644 --- a/conditional/blueprints/attendance.py +++ b/conditional/blueprints/attendance.py @@ -1,6 +1,5 @@ from datetime import datetime -import uuid import structlog from flask import Blueprint, jsonify, redirect, request @@ -25,7 +24,7 @@ from conditional.util.flask import render_template -from conditional import db +from conditional import db, start_of_year logger = structlog.get_logger() @@ -34,9 +33,8 @@ @attendance_bp.route('/attendance/ts_members') def get_all_members(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='retrieve technical seminar attendance list') + log = logger.new(request=request) + log.info('Retrieve Technical Seminar Attendance List') members = ldap_get_current_students() @@ -61,13 +59,20 @@ def get_all_members(): @attendance_bp.route('/attendance/hm_members') def get_non_alumni_non_coop(internal=False): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='retrieve house meeting attendance list') + log = logger.new(request=request) + log.info('Retrieve House Meeting Attendance List') # Get all active members as a base house meeting attendance. active_members = ldap_get_active_members() - coop_members = [u.uid for u in CurrentCoops.query.all()] + + if datetime.today() < datetime(start_of_year().year, 12, 31): + semester = 'Fall' + else: + semester = 'Spring' + + coop_members = [u.uid for u in CurrentCoops.query.filter( + CurrentCoops.date_created > start_of_year(), + CurrentCoops.semester == semester).all()] eligible_members = [ { @@ -97,9 +102,8 @@ def get_non_alumni_non_coop(internal=False): @attendance_bp.route('/attendance/cm_members') def get_non_alumni(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='retrieve committee meeting attendance list') + log = logger.new(request=request) + log.info('Retrieve Committee Meeting Attendance List') current_students = ldap_get_current_students() @@ -124,9 +128,8 @@ def get_non_alumni(): @attendance_bp.route('/attendance_cm') def display_attendance_cm(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display committee meeting attendance page') + log = logger.new(request=request) + log.info('Display Committee Meeting Attendance Page') return render_template(request, 'attendance_cm.html', @@ -136,9 +139,8 @@ def display_attendance_cm(): @attendance_bp.route('/attendance_ts') def display_attendance_ts(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display technical seminar attendance page') + log = logger.new(request=request) + log.info('Display Technical Seminar Attendance Page') return render_template(request, 'attendance_ts.html', @@ -148,9 +150,8 @@ def display_attendance_ts(): @attendance_bp.route('/attendance_hm') def display_attendance_hm(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display house meeting attendance page') + log = logger.new(request=request) + log.info('Display House Meeting Attendance Page') user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) @@ -166,9 +167,8 @@ def display_attendance_hm(): @attendance_bp.route('/attendance/submit/cm', methods=['POST']) def submit_committee_attendance(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit committee meeting attendance') + log = logger.new(request=request) + user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) @@ -180,6 +180,8 @@ def submit_committee_attendance(): f_attendees = post_data['freshmen'] timestamp = post_data['timestamp'] + log.info('Submit {} Meeting Attendance'.format(committee)) + timestamp = datetime.strptime(timestamp, "%Y-%m-%d") meeting = CommitteeMeeting(committee, timestamp, approved) @@ -188,15 +190,11 @@ def submit_committee_attendance(): db.session.refresh(meeting) for m in m_attendees: - logger.info('backend', - action=("gave attendance to %s for %s" % (m, committee)) - ) + log.info('Gave Attendance to {} for {}'.format(m, committee)) db.session.add(MemberCommitteeAttendance(m, meeting.id)) for f in f_attendees: - logger.info('backend', - action=("gave attendance to freshman-%s for %s" % (f, committee)) - ) + log.info('Gave Attendance to freshman-{} for {}'.format(f, committee)) db.session.add(FreshmanCommitteeAttendance(f, meeting.id)) db.session.commit() @@ -205,10 +203,8 @@ def submit_committee_attendance(): @attendance_bp.route('/attendance/submit/ts', methods=['POST']) def submit_seminar_attendance(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4()) - ) - log.info('api', action='submit technical seminar attendance') + log = logger.new(request=request) + log.info('Submit Technical Seminar Attendance') user_name = request.headers.get('x-webauth-user') @@ -230,15 +226,11 @@ def submit_seminar_attendance(): db.session.refresh(seminar) for m in m_attendees: - logger.info('backend', - action=("gave attendance to %s for %s" % (m, seminar_name)) - ) + log.info('Gave Attendance to {} for {}'.format(m, seminar_name)) db.session.add(MemberSeminarAttendance(m, seminar.id)) for f in f_attendees: - logger.info('backend', - action=("gave attendance to freshman-%s for %s" % (f, seminar_name)) - ) + log.info('Gave Attendance to freshman-{} for {}'.format(f, seminar_name)) db.session.add(FreshmanSeminarAttendance(f, seminar.id)) db.session.commit() @@ -247,9 +239,8 @@ def submit_seminar_attendance(): @attendance_bp.route('/attendance/submit/hm', methods=['POST']) def submit_house_attendance(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit house meeting attendance') + log = logger.new(request=request) + log.info('Submit House Meeting Attendance') # status: Attended | Excused | Absent @@ -271,11 +262,10 @@ def submit_house_attendance(): if "members" in post_data: for m in post_data['members']: - logger.info('backend', - action=( - "gave %s to %s for %s house meeting" % ( - m['status'], m['uid'], timestamp.strftime("%Y-%m-%d"))) - ) + log.info('Marked {} {} for House Meeting on {}'.format( + m['uid'], + m['status'], + timestamp.strftime("%Y-%m-%d"))) db.session.add(MemberHouseMeetingAttendance( m['uid'], meeting.id, @@ -284,10 +274,10 @@ def submit_house_attendance(): if "freshmen" in post_data: for f in post_data['freshmen']: - logger.info('backend', - action=("gave %s to freshman-%s for %s house meeting" % ( - f['status'], f['id'], timestamp.strftime("%Y-%m-%d"))) - ) + log.info('Marked freshman-{} {} for House Meeting on {}'.format( + f['id'], + f['status'], + timestamp.strftime("%Y-%m-%d"))) db.session.add(FreshmanHouseMeetingAttendance( f['id'], meeting.id, @@ -300,6 +290,7 @@ def submit_house_attendance(): @attendance_bp.route('/attendance/alter/hm//', methods=['GET']) def alter_house_attendance(uid, hid): + log = logger.new(request=request) user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) @@ -307,6 +298,7 @@ def alter_house_attendance(uid, hid): return "must be evals", 403 if not uid.isdigit(): + log.info('Mark {} Present for House Meeting ID: {}'.format(uid, hid)) member_meeting = MemberHouseMeetingAttendance.query.filter( MemberHouseMeetingAttendance.uid == uid, MemberHouseMeetingAttendance.meeting_id == hid @@ -315,6 +307,7 @@ def alter_house_attendance(uid, hid): db.session.commit() return jsonify({"success": True}), 200 + log.info('Mark freshman-{} Present for House Meeting ID: {}'.format(uid, hid)) freshman_meeting = FreshmanHouseMeetingAttendance.query.filter( FreshmanHouseMeetingAttendance.fid == uid, FreshmanHouseMeetingAttendance.meeting_id == hid @@ -327,9 +320,7 @@ def alter_house_attendance(uid, hid): @attendance_bp.route('/attendance/alter/hm//', methods=['POST']) def alter_house_excuse(uid, hid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='edit house meeting excuse') + log = logger.new(request=request) user_name = request.headers.get('x-webauth-user') @@ -341,10 +332,10 @@ def alter_house_excuse(uid, hid): hm_status = post_data['status'] hm_excuse = post_data['excuse'] - logger.info('backend', action="edit hm %s status: %s excuse: %s" % - (hid, hm_status, hm_excuse)) + if not uid.isdigit(): + log.info('Mark {} as {} for HM ID: {}'.format(uid, hm_status, hid)) MemberHouseMeetingAttendance.query.filter( MemberHouseMeetingAttendance.uid == uid, MemberHouseMeetingAttendance.meeting_id == hid @@ -353,6 +344,7 @@ def alter_house_excuse(uid, hid): 'attendance_status': hm_status }) else: + log.info('Mark {} as {} for HM ID: {}'.format(uid, hm_status, hid)) FreshmanHouseMeetingAttendance.query.filter( FreshmanHouseMeetingAttendance.fid == uid, FreshmanHouseMeetingAttendance.meeting_id == hid @@ -394,68 +386,70 @@ def get_seminar_attendees(meeting_id): FreshmanAccount.id == freshman).first().name) return attendees - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) + log = logger.new(request=request) user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) if not ldap_is_eboard(account): return "must be eboard", 403 - if request.method == 'GET': - page = request.args.get('page', 1) - log.info('api', action='view past attendance submitions') - offset = 0 if int(page) == 1 else ((int(page)-1)*10) - limit = int(page)*10 - all_cm = [{"id": m.id, - "name": m.committee, - "dt_obj": m.timestamp, - "date": m.timestamp.strftime("%a %m/%d/%Y"), - "attendees": get_meeting_attendees(m.id), - "type": "cm" - } for m in CommitteeMeeting.query.filter( - CommitteeMeeting.approved).all()] - all_ts = [{"id": m.id, - "name": m.name, - "dt_obj": m.timestamp, - "date": m.timestamp.strftime("%a %m/%d/%Y"), - "attendees": get_seminar_attendees(m.id), - "type": "ts" - } for m in TechnicalSeminar.query.filter( - TechnicalSeminar.approved).all()] - pend_cm = [{"id": m.id, - "name": m.committee, - "dt_obj": m.timestamp, - "date": m.timestamp.strftime("%a %m/%d/%Y"), - "attendees": get_meeting_attendees(m.id) - } for m in CommitteeMeeting.query.filter( - CommitteeMeeting.approved == False).all()] # pylint: disable=singleton-comparison - pend_ts = [{"id": m.id, - "name": m.name, - "dt_obj": m.timestamp, - "date": m.timestamp.strftime("%a %m/%d/%Y"), - "attendees": get_seminar_attendees(m.id) - } for m in TechnicalSeminar.query.filter( - TechnicalSeminar.approved == False).all()] # pylint: disable=singleton-comparison - all_meetings = sorted((all_cm + all_ts), key=lambda k: k['dt_obj'], reverse=True)[offset:limit] - if len(all_cm) % 10 != 0: - total_pages = (int(len(all_cm) / 10) + 1) - else: - total_pages = (int(len(all_cm) / 10)) - return render_template(request, - 'attendance_history.html', - username=user_name, - history=all_meetings, - pending_cm=pend_cm, - pending_ts=pend_ts, - num_pages=total_pages, - current_page=int(page)) + + page = request.args.get('page', 1) + log.info('View Past Attendance Submitions') + offset = 0 if int(page) == 1 else ((int(page)-1)*10) + limit = int(page)*10 + all_cm = [{"id": m.id, + "name": m.committee, + "dt_obj": m.timestamp, + "date": m.timestamp.strftime("%a %m/%d/%Y"), + "attendees": get_meeting_attendees(m.id), + "type": "cm" + } for m in CommitteeMeeting.query.filter( + CommitteeMeeting.timestamp > start_of_year(), + CommitteeMeeting.approved).all()] + all_ts = [{"id": m.id, + "name": m.name, + "dt_obj": m.timestamp, + "date": m.timestamp.strftime("%a %m/%d/%Y"), + "attendees": get_seminar_attendees(m.id), + "type": "ts" + } for m in TechnicalSeminar.query.filter( + TechnicalSeminar.timestamp > start_of_year(), + TechnicalSeminar.approved).all()] + pend_cm = [{"id": m.id, + "name": m.committee, + "dt_obj": m.timestamp, + "date": m.timestamp.strftime("%a %m/%d/%Y"), + "attendees": get_meeting_attendees(m.id) + } for m in CommitteeMeeting.query.filter( + CommitteeMeeting.timestamp > start_of_year(), + CommitteeMeeting.approved == False).all()] # pylint: disable=singleton-comparison + pend_ts = [{"id": m.id, + "name": m.name, + "dt_obj": m.timestamp, + "date": m.timestamp.strftime("%a %m/%d/%Y"), + "attendees": get_seminar_attendees(m.id) + } for m in TechnicalSeminar.query.filter( + TechnicalSeminar.timestamp > start_of_year(), + TechnicalSeminar.approved == False).all()] # pylint: disable=singleton-comparison + all_meetings = sorted((all_cm + all_ts), key=lambda k: k['dt_obj'], reverse=True)[offset:limit] + if len(all_cm) % 10 != 0: + total_pages = (int(len(all_cm) / 10) + 1) + else: + total_pages = (int(len(all_cm) / 10)) + return render_template(request, + 'attendance_history.html', + username=user_name, + history=all_meetings, + pending_cm=pend_cm, + pending_ts=pend_ts, + num_pages=total_pages, + current_page=int(page)) @attendance_bp.route('/attendance/alter/cm/', methods=['POST']) def alter_committee_attendance(cid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='edit committee meeting attendance') + log = logger.new(request=request) + log.info('Edit Committee Meeting Attendance') user_name = request.headers.get('x-webauth-user') @@ -487,9 +481,8 @@ def alter_committee_attendance(cid): @attendance_bp.route('/attendance/alter/ts/', methods=['POST']) def alter_seminar_attendance(sid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='edit technical seminar attendance') + log = logger.new(request=request) + log.info('Edit Technical Seminar Attendance') user_name = request.headers.get('x-webauth-user') @@ -536,9 +529,8 @@ def get_cm_attendees(sid): return jsonify({"attendees": attendees}), 200 elif request.method == 'DELETE': - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='delete technical seminar') + log = logger.new(request=request) + log.info('Delete Technical Seminar {}'.format(sid)) user_name = request.headers.get('x-webauth-user') @@ -576,9 +568,8 @@ def get_ts_attendees(cid): return jsonify({"attendees": attendees}), 200 elif request.method == 'DELETE': - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='delete committee meeting') + log = logger.new(request=request) + log.info('Delete Committee Meeting {}'.format(cid)) user_name = request.headers.get('x-webauth-user') @@ -601,9 +592,8 @@ def get_ts_attendees(cid): @attendance_bp.route('/attendance/cm//approve', methods=['POST']) def approve_cm(cid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='approve committee meeting attendance') + log = logger.new(request=request) + log.info('Approve Committee Meeting {} Attendance'.format(cid)) user_name = request.headers.get('x-webauth-user') @@ -621,9 +611,8 @@ def approve_cm(cid): @attendance_bp.route('/attendance/ts//approve', methods=['POST']) def approve_ts(sid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='approve committee meeting attendance') + log = logger.new(request=request) + log.info('Approve Technical Seminar {} Attendance'.format(sid)) user_name = request.headers.get('x-webauth-user') diff --git a/conditional/blueprints/cache_management.py b/conditional/blueprints/cache_management.py index 559063ad..907b8a8b 100644 --- a/conditional/blueprints/cache_management.py +++ b/conditional/blueprints/cache_management.py @@ -30,7 +30,8 @@ def restart_app(): if not ldap_is_rtp(account): return redirect("/dashboard") - logger.info('api', action='restart conditional') + log = logger.new(request=request) + log.info('Restart Conditional') os.kill(os.getpid(), signal.SIGINT) return "application restarted", 200 @@ -40,10 +41,11 @@ def clear_cache(): user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) - if not ldap_is_eval_director(account) or not ldap_is_rtp(account): + if not ldap_is_eval_director(account) and not ldap_is_rtp(account): return redirect("/dashboard") - logger.info('api', action='purge system cache') + log = logger.new(request=request) + log.info('Purge All Caches') _ldap_is_member_of_directorship.cache_clear() ldap_get_member.cache_clear() diff --git a/conditional/blueprints/co_op.py b/conditional/blueprints/co_op.py new file mode 100644 index 00000000..9dbe8c3f --- /dev/null +++ b/conditional/blueprints/co_op.py @@ -0,0 +1,73 @@ +import structlog + +from flask import Blueprint, request, jsonify + +from conditional.util.flask import render_template + +from conditional.util.ldap import ldap_get_member, ldap_is_eval_director + +from conditional.models.models import CurrentCoops + +from conditional import db, start_of_year + +co_op_bp = Blueprint('co_op_bp', __name__) + +logger = structlog.get_logger() + +@co_op_bp.route('/co_op/') +def display_co_op_form(): + log = logger.new(request=request) + log.info('Display Co-Op Submission Page') + + # get user data + user_name = request.headers.get('x-webauth-user') + co_op = CurrentCoops.query.filter( + CurrentCoops.uid == user_name, CurrentCoops.date_created > start_of_year()).first() + + return render_template(request, + 'co_op.html', + username=user_name, + year=start_of_year().year, + on_coop=co_op) + + +@co_op_bp.route('/co_op/submit', methods=['POST']) +def submit_co_op_form(): + log = logger.new(request=request) + + user_name = request.headers.get('x-webauth-user') + + post_data = request.get_json() + semester = post_data['semester'] + + log.info('Submit {} Co-Op'.format(semester)) + + if CurrentCoops.query.filter(CurrentCoops.uid == user_name, CurrentCoops.date_created > start_of_year()).first(): + return "User has already submitted this form!", 403 + + co_op = CurrentCoops(uid=user_name, semester=semester) + db.session.add(co_op) + db.session.flush() + db.session.commit() + + return jsonify({"success": True}), 200 + + +@co_op_bp.route('/co_op/', methods=['DELETE']) +def delete_co_op(uid): + log = logger.new(request=request) + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + + if not ldap_is_eval_director(account): + return "must be eval director", 403 + + log.info('Delete {}\'s Co-Op'.format(uid)) + + CurrentCoops.query.filter(CurrentCoops.uid == uid, CurrentCoops.date_created > start_of_year()).delete() + + db.session.flush() + db.session.commit() + + return jsonify({"success": True}), 200 diff --git a/conditional/blueprints/conditional.py b/conditional/blueprints/conditional.py index d09fd86a..52696fc7 100644 --- a/conditional/blueprints/conditional.py +++ b/conditional/blueprints/conditional.py @@ -1,5 +1,3 @@ -import uuid - from datetime import datetime import structlog @@ -10,7 +8,7 @@ from conditional.util.ldap import ldap_is_eval_director from conditional.util.flask import render_template -from conditional.models.models import Conditional +from conditional.models.models import Conditional, SpringEval, FreshmanEvalData from conditional import db @@ -21,9 +19,8 @@ @conditionals_bp.route('/conditionals/') def display_conditionals(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display conditional listing page') + log = logger.new(request=request) + log.info('Display Conditional Listing Page') # get user data user_name = request.headers.get('x-webauth-user') @@ -48,9 +45,7 @@ def display_conditionals(): @conditionals_bp.route('/conditionals/create', methods=['POST']) def create_conditional(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='create new conditional') + log = logger.new(request=request) user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) @@ -63,8 +58,23 @@ def create_conditional(): uid = post_data['uid'] description = post_data['description'] due_date = datetime.strptime(post_data['dueDate'], "%Y-%m-%d") + log.info('Create a new conditional for {}'.format(uid)) + if post_data['evaluation'] == 'spring': + current_eval = SpringEval.query.filter(SpringEval.status == "Pending", + SpringEval.uid == uid, + SpringEval.active == True).first().id # pylint: disable=singleton-comparison + db.session.add(Conditional(uid, description, due_date, s_eval=current_eval)) + elif post_data['evaluation'] == 'intro': + if uid.isdigit(): + current_eval = FreshmanEvalData.query.filter(FreshmanEvalData.freshman_eval_result == "Pending", + FreshmanEvalData.id == uid).first().id + else: + current_eval = FreshmanEvalData.query.filter(FreshmanEvalData.freshman_eval_result == "Pending", + FreshmanEvalData.uid == uid).first().id + db.session.add(Conditional(uid, description, due_date, i_eval=current_eval)) + else: + db.session.add(Conditional(uid, description, due_date)) - db.session.add(Conditional(uid, description, due_date)) db.session.flush() db.session.commit() @@ -73,9 +83,7 @@ def create_conditional(): @conditionals_bp.route('/conditionals/review', methods=['POST']) def conditional_review(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='review a conditional') + log = logger.new(request=request) # get user data user_name = request.headers.get('x-webauth-user') @@ -88,13 +96,24 @@ def conditional_review(): cid = post_data['id'] status = post_data['status'] - logger.info(action="updated conditional-%s to %s" % (cid, status)) - Conditional.query.filter( - Conditional.id == cid). \ - update( + log.info('Updated conditional-{} to {}'.format(cid, status)) + conditional = Conditional.query.filter(Conditional.id == cid) + cond_obj = conditional.first() + + conditional.update( { 'status': status }) + if cond_obj.s_evaluation: + SpringEval.query.filter(SpringEval.id == cond_obj.s_evaluation).update( + { + 'status': status + }) + elif cond_obj.i_evaluation: + FreshmanEvalData.query.filter(FreshmanEvalData.id == cond_obj.i_evaluation).update( + { + 'freshman_eval_result': status + }) db.session.flush() db.session.commit() @@ -103,9 +122,8 @@ def conditional_review(): @conditionals_bp.route('/conditionals/delete/', methods=['DELETE']) def conditional_delete(cid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='delete conditional') + log = logger.new(request=request) + log.info('Delete conditional-{}'.format(cid)) user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) diff --git a/conditional/blueprints/dashboard.py b/conditional/blueprints/dashboard.py index bea2c7fd..9ca7b8ad 100644 --- a/conditional/blueprints/dashboard.py +++ b/conditional/blueprints/dashboard.py @@ -1,4 +1,3 @@ -import uuid import structlog from flask import Blueprint, request @@ -6,10 +5,10 @@ from conditional.util.ldap import ldap_is_onfloor from conditional.util.ldap import ldap_is_active from conditional.util.ldap import ldap_is_intromember +from conditional.util.ldap import ldap_is_current_student from conditional.util.ldap import ldap_get_member from conditional.util.ldap import ldap_get_active_members -from conditional.models.models import MemberCommitteeAttendance from conditional.models.models import MemberHouseMeetingAttendance from conditional.models.models import MajorProject from conditional.models.models import Conditional @@ -19,7 +18,9 @@ from conditional.util.housing import get_queue_position from conditional.util.flask import render_template -from conditional.util.member import get_freshman_data, get_voting_members +from conditional.util.member import get_freshman_data, get_voting_members, get_cm, get_hm + +from conditional import start_of_year logger = structlog.get_logger() @@ -28,9 +29,8 @@ @dashboard_bp.route('/dashboard/') def display_dashboard(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display dashboard') + log = logger.new(request=request) + log.info('display dashboard') # Get username from headers. username = request.headers.get('x-webauth-user') @@ -45,6 +45,8 @@ def display_dashboard(): data['active'] = ldap_is_active(member) data['onfloor'] = ldap_is_onfloor(member) data['voting'] = bool(member.uid in can_vote) + data['student'] = ldap_is_current_student(member) + data['voting_count'] = {"Voting Members": len(can_vote), "Active Members": len(ldap_get_active_members())} @@ -55,18 +57,13 @@ def display_dashboard(): data['freshman'] = False spring = {} - c_meetings = [m.meeting_id for m in - MemberCommitteeAttendance.query.filter( - MemberCommitteeAttendance.uid == member.uid - ) if CommitteeMeeting.query.filter( - CommitteeMeeting.id == m.meeting_id).first().approved] + c_meetings = get_cm(member) spring['committee_meetings'] = len(c_meetings) - h_meetings = [(m.meeting_id, m.attendance_status) for m in - MemberHouseMeetingAttendance.query.filter( - MemberHouseMeetingAttendance.uid == member.uid)] + h_meetings = [(m.meeting_id, m.attendance_status) for m in get_hm(member)] spring['hm_missed'] = len([h for h in h_meetings if h[1] == "Absent"]) - eval_entry = SpringEval.query.filter(SpringEval.uid == member.uid - and SpringEval.active).first() + eval_entry = SpringEval.query.filter(SpringEval.uid == member.uid, + SpringEval.date_created > start_of_year(), + SpringEval.active == True).first() # pylint: disable=singleton-comparison if eval_entry is not None: spring['status'] = eval_entry.status else: @@ -92,7 +89,8 @@ def display_dashboard(): 'status': p.status, 'description': p.description } for p in - MajorProject.query.filter(MajorProject.uid == member.uid)] + MajorProject.query.filter(MajorProject.uid == member.uid, + MajorProject.date > start_of_year())] data['major_projects_count'] = len(data['major_projects']) @@ -112,7 +110,9 @@ def display_dashboard(): 'description': c.description, 'status': c.status } for c in - Conditional.query.filter(Conditional.uid == member.uid)] + Conditional.query.filter( + Conditional.uid == member.uid, + Conditional.date_due > start_of_year())] data['conditionals'] = conditionals data['conditionals_len'] = len(conditionals) @@ -121,18 +121,23 @@ def display_dashboard(): 'type': m.committee, 'datetime': m.timestamp.date() } for m in CommitteeMeeting.query.filter( - CommitteeMeeting.id.in_(c_meetings) + CommitteeMeeting.id.in_(c_meetings), + CommitteeMeeting.timestamp > start_of_year() )] hm_attendance = [ { 'reason': m.excuse, - 'datetime': HouseMeeting.query.filter( - HouseMeeting.id == m.meeting_id).first().date + 'datetime': m.date } for m in - MemberHouseMeetingAttendance.query.filter( - MemberHouseMeetingAttendance.uid == member.uid - ).filter(MemberHouseMeetingAttendance.attendance_status == "Absent")] + MemberHouseMeetingAttendance.query.outerjoin( + HouseMeeting, + MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id).with_entities( + MemberHouseMeetingAttendance.excuse, + HouseMeeting.date).filter( + MemberHouseMeetingAttendance.uid == member.uid, + MemberHouseMeetingAttendance.attendance_status == "Absent", + HouseMeeting.date > start_of_year())] data['cm_attendance'] = cm_attendance data['cm_attendance_len'] = len(cm_attendance) diff --git a/conditional/blueprints/housing.py b/conditional/blueprints/housing.py index 7326309d..8aec1eb0 100644 --- a/conditional/blueprints/housing.py +++ b/conditional/blueprints/housing.py @@ -1,4 +1,3 @@ -import uuid import structlog from flask import Blueprint, request, jsonify @@ -11,6 +10,7 @@ from conditional.util.ldap import ldap_get_member from conditional.util.ldap import ldap_get_roomnumber from conditional.util.ldap import ldap_get_current_students +from conditional.util.ldap import ldap_set_active from conditional.util.flask import render_template @@ -24,9 +24,8 @@ @housing_bp.route('/housing') def display_housing(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display housing') + log = logger.new(request=request) + log.info('Display Housing Board') # get user data user_name = request.headers.get('x-webauth-user') @@ -70,9 +69,8 @@ def display_housing(): @housing_bp.route('/housing/in_queue', methods=['PUT']) def change_queue_state(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='add or remove member from housing queue') + log = logger.new(request=request) + username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -85,9 +83,11 @@ def change_queue_state(): if uid: if post_data.get('inQueue', False): + log.info('Add {} to Housing Queue'.format(uid)) queue_obj = InHousingQueue(uid=uid) db.session.add(queue_obj) else: + log.info('Remove {} from Housing Queue'.format(uid)) InHousingQueue.query.filter_by(uid=uid).delete() db.session.flush() @@ -97,9 +97,7 @@ def change_queue_state(): @housing_bp.route('/housing/update/', methods=['POST']) def change_room_numbers(rmnumber): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='mass housing update') + log = logger.new(request=request) username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -117,13 +115,15 @@ def change_room_numbers(rmnumber): if occupant != "": account = ldap_get_member(occupant) account.roomNumber = rmnumber - log.info('api', action='%s assigned to room %s' % (occupant, rmnumber)) + log.info('{} assigned to room {}'.format(occupant, rmnumber)) + ldap_set_active(account) + log.info('{} marked as active because of room assignment'.format(occupant)) # Delete any old occupants that are no longer in room. for old_occupant in [account for account in current_students if ldap_get_roomnumber(account) == str(rmnumber) and account.uid not in update["occupants"]]: + log.info('{} removed from room {}'.format(old_occupant.uid, old_occupant.roomNumber)) old_occupant.roomNumber = None - log.info('api', action='%s removed from room' % old_occupant.uid) return jsonify({"success": True}), 200 @@ -138,3 +138,22 @@ def get_occupants(rmnumber): occupants = [account.uid for account in current_students if ldap_get_roomnumber(account) == str(rmnumber)] return jsonify({"room": rmnumber, "occupants": occupants}), 200 + + +@housing_bp.route('/housing', methods=['DELETE']) +def clear_all_rooms(): + log = logger.new(request=request) + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + + if not ldap_is_eval_director(account): + return "must be eval director", 403 + # Get list of current students. + current_students = ldap_get_current_students() + + # Find the current occupants and clear them. + for occupant in current_students: + log.info('{} removed from room {}'.format(occupant.uid, occupant.roomNumber)) + occupant.roomNumber = None + return jsonify({"success": True}), 200 diff --git a/conditional/blueprints/intro_evals.py b/conditional/blueprints/intro_evals.py index cf76469f..cd11732b 100644 --- a/conditional/blueprints/intro_evals.py +++ b/conditional/blueprints/intro_evals.py @@ -1,14 +1,12 @@ from datetime import datetime -import uuid + import structlog from flask import Blueprint, request from conditional.util.ldap import ldap_get_intro_members - from conditional.models.models import FreshmanCommitteeAttendance -from conditional.models.models import MemberCommitteeAttendance from conditional.models.models import CommitteeMeeting from conditional.models.models import FreshmanAccount from conditional.models.models import FreshmanEvalData @@ -20,6 +18,10 @@ from conditional.models.models import TechnicalSeminar from conditional.util.flask import render_template +from conditional.util.member import get_cm, get_hm + +from conditional import start_of_year + intro_evals_bp = Blueprint('intro_evals_bp', __name__) logger = structlog.get_logger() @@ -27,20 +29,14 @@ @intro_evals_bp.route('/intro_evals/') def display_intro_evals(internal=False): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display intro evals listing') + log = logger.new(request=request) + log.info('Display Intro Evals Listing') # get user data - def get_uid_cm_count(member_id): - return len([a for a in MemberCommitteeAttendance.query.filter( - MemberCommitteeAttendance.uid == member_id) - if CommitteeMeeting.query.filter(CommitteeMeeting.id == a.meeting_id).approved]) - def get_fid_cm_count(member_id): return len([a for a in FreshmanCommitteeAttendance.query.filter( FreshmanCommitteeAttendance.fid == member_id) - if CommitteeMeeting.query.filter(CommitteeMeeting.id == a.meeting_id).approved]) + if CommitteeMeeting.query.filter(CommitteeMeeting.id == a.meeting_id).first().approved]) user_name = None if not internal: @@ -52,6 +48,7 @@ def get_fid_cm_count(member_id): # freshmen who don't have accounts fids = [f for f in FreshmanAccount.query.filter( + FreshmanAccount.eval_date > start_of_year(), FreshmanAccount.eval_date > datetime.now())] for fid in fids: @@ -92,7 +89,7 @@ def get_fid_cm_count(member_id): TechnicalSeminar.id.in_( [a.seminar_id for a in FreshmanSeminarAttendance.query.filter( FreshmanSeminarAttendance.fid == fid.id) - if TechnicalSeminar.query.filter(TechnicalSeminar.id == a.seminar_id).approved] + if TechnicalSeminar.query.filter(TechnicalSeminar.id == a.seminar_id).first().approved] )) ], 'social_events': '', @@ -108,6 +105,7 @@ def get_fid_cm_count(member_id): uid = member.uid name = member.cn freshman_data = FreshmanEvalData.query.filter( + FreshmanEvalData.eval_date > start_of_year(), FreshmanEvalData.uid == uid).first() if freshman_data is None: @@ -115,26 +113,21 @@ def get_fid_cm_count(member_id): elif freshman_data.freshman_eval_result != "Pending" and internal: continue - h_meetings = [m.meeting_id for m in - MemberHouseMeetingAttendance.query.filter( - MemberHouseMeetingAttendance.uid == uid - ).filter( - MemberHouseMeetingAttendance.attendance_status == "Absent" - )] - member = { + h_meetings = [m.meeting_id for m in get_hm(member)] + member_info = { 'name': name, 'uid': uid, 'eval_date': freshman_data.eval_date.strftime("%Y-%m-%d"), 'signatures_missed': freshman_data.signatures_missed, - 'committee_meetings': get_uid_cm_count(uid), - 'committee_meetings_passed': get_uid_cm_count(uid) >= 10, + 'committee_meetings': len(get_cm(member)), + 'committee_meetings_passed': len(get_cm(member)) >= 10, 'house_meetings_missed': [ { "date": m.date.strftime("%Y-%m-%d"), "reason": MemberHouseMeetingAttendance.query.filter( - MemberHouseMeetingAttendance.uid == uid).filter( + MemberHouseMeetingAttendance.uid == uid, MemberHouseMeetingAttendance.meeting_id == m.id).first().excuse } for m in HouseMeeting.query.filter( @@ -146,7 +139,9 @@ def get_fid_cm_count(member_id): TechnicalSeminar.id.in_( [a.seminar_id for a in MemberSeminarAttendance.query.filter( MemberSeminarAttendance.uid == uid) - if TechnicalSeminar.query.filter(TechnicalSeminar.id == a.seminar_id).approved] + if TechnicalSeminar.query.filter( + TechnicalSeminar.id == a.seminar_id, + TechnicalSeminar.timestamp > start_of_year()).first().approved] )) ], 'social_events': freshman_data.social_events, @@ -155,7 +150,7 @@ def get_fid_cm_count(member_id): 'ldap_account': True, 'status': freshman_data.freshman_eval_result } - ie_members.append(member) + ie_members.append(member_info) ie_members.sort(key=lambda x: x['freshman_project'] == "Passed") ie_members.sort(key=lambda x: len(x['house_meetings_missed'])) diff --git a/conditional/blueprints/intro_evals_form.py b/conditional/blueprints/intro_evals_form.py index 6a3910c6..4eb9e082 100644 --- a/conditional/blueprints/intro_evals_form.py +++ b/conditional/blueprints/intro_evals_form.py @@ -1,4 +1,3 @@ -import uuid import structlog from flask import Blueprint, request, redirect, jsonify @@ -18,9 +17,8 @@ @intro_evals_form_bp.route('/intro_evals_form/') def display_intro_evals_form(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display intro evals form') + log = logger.new(request=request) + log.info('Display Intro Evals Form') # get user data user_name = request.headers.get('x-webauth-user') @@ -42,9 +40,8 @@ def display_intro_evals_form(): @intro_evals_form_bp.route('/intro_evals/submit', methods=['POST']) def submit_intro_evals(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit intro evals form') + log = logger.new(request=request) + log.info('Submit Intro Evals Form') user_name = request.headers.get('x-webauth-user') diff --git a/conditional/blueprints/logs.py b/conditional/blueprints/logs.py new file mode 100644 index 00000000..8b2b623a --- /dev/null +++ b/conditional/blueprints/logs.py @@ -0,0 +1,31 @@ +import structlog + +from flask import Blueprint, request + +from conditional.models.models import UserLog + +from conditional.util.ldap import ldap_is_eboard +from conditional.util.ldap import ldap_is_rtp +from conditional.util.ldap import ldap_get_member + +from conditional.util.flask import render_template + +logger = structlog.get_logger() + +log_bp = Blueprint('log_bp', __name__) + +@log_bp.route('/logs') +def display_logs(): + log = logger.new(request=request) + log.info('Display Logs') + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + log.info(account.displayName) + + if not ldap_is_eboard(account) and not ldap_is_rtp(account): + return "must be rtp or eboard", 403 + + logs = UserLog.query.all() + + return render_template(request, "logs.html", logs=logs, username=username) diff --git a/conditional/blueprints/major_project_submission.py b/conditional/blueprints/major_project_submission.py index 363bd59b..e8fd6be7 100644 --- a/conditional/blueprints/major_project_submission.py +++ b/conditional/blueprints/major_project_submission.py @@ -1,4 +1,3 @@ -import uuid import structlog from flask import Blueprint, request, jsonify, redirect @@ -9,7 +8,7 @@ from conditional.util.ldap import ldap_get_member from conditional.util.flask import render_template -from conditional import db +from conditional import db, start_of_year from sqlalchemy import desc @@ -20,9 +19,8 @@ @major_project_bp.route('/major_project/') def display_major_project(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display major project form') + log = logger.new(request=request) + log.info('Display Major Project Page') # get user data @@ -38,7 +36,9 @@ def display_major_project(): 'id': p.id, 'is_owner': bool(user_name == p.uid) } for p in - MajorProject.query.order_by(desc(MajorProject.id))] + MajorProject.query.filter( + MajorProject.date > start_of_year()).order_by( + desc(MajorProject.id))] major_projects_len = len(major_projects) # return names in 'first last (username)' format @@ -51,9 +51,8 @@ def display_major_project(): @major_project_bp.route('/major_project/submit', methods=['POST']) def submit_major_project(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit major project') + log = logger.new(request=request) + log.info('Submit Major Project') user_name = request.headers.get('x-webauth-user') @@ -72,9 +71,7 @@ def submit_major_project(): @major_project_bp.route('/major_project/review', methods=['POST']) def major_project_review(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='review major project') + log = logger.new(request=request) # get user data user_name = request.headers.get('x-webauth-user') @@ -87,6 +84,8 @@ def major_project_review(): pid = post_data['id'] status = post_data['status'] + log.info('{} Major Project ID: {}'.format(status, pid)) + print(post_data) MajorProject.query.filter( MajorProject.id == pid). \ @@ -101,9 +100,8 @@ def major_project_review(): @major_project_bp.route('/major_project/delete/', methods=['DELETE']) def major_project_delete(pid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='review major project') + log = logger.new(request=request) + log.info('Delete Major Project ID: {}'.format(pid)) # get user data user_name = request.headers.get('x-webauth-user') diff --git a/conditional/blueprints/member_management.py b/conditional/blueprints/member_management.py index 146526f9..e0da46ca 100644 --- a/conditional/blueprints/member_management.py +++ b/conditional/blueprints/member_management.py @@ -1,12 +1,14 @@ import csv import io -import uuid +import re from datetime import datetime import structlog -from flask import Blueprint, request, jsonify, abort +from flask import Blueprint, request, jsonify, abort, make_response + +from conditional import app from conditional.models.models import FreshmanAccount from conditional.models.models import FreshmanEvalData @@ -20,6 +22,7 @@ from conditional.models.models import EvalSettings from conditional.models.models import OnFloorStatusAssigned from conditional.models.models import SpringEval +from conditional.models.models import CurrentCoops from conditional.blueprints.cache_management import clear_members_cache from conditional.blueprints.intro_evals import display_intro_evals @@ -28,12 +31,16 @@ from conditional.util.ldap import ldap_is_financial_director from conditional.util.ldap import ldap_is_active from conditional.util.ldap import ldap_is_onfloor +from conditional.util.ldap import ldap_is_current_student from conditional.util.ldap import ldap_set_roomnumber from conditional.util.ldap import ldap_set_active from conditional.util.ldap import ldap_set_inactive from conditional.util.ldap import ldap_set_housingpoints +from conditional.util.ldap import ldap_set_current_student +from conditional.util.ldap import ldap_set_non_current_student from conditional.util.ldap import ldap_get_active_members from conditional.util.ldap import ldap_get_member +from conditional.util.ldap import ldap_get_current_students from conditional.util.ldap import _ldap_add_member_to_group as ldap_add_member_to_group from conditional.util.ldap import _ldap_remove_member_from_group as ldap_remove_member_from_group from conditional.util.ldap import _ldap_is_member_of_group as ldap_is_member_of_group @@ -42,7 +49,7 @@ from conditional.models.models import attendance_enum from conditional.util.member import get_members_info, get_onfloor_members -from conditional import db +from conditional import db, start_of_year logger = structlog.get_logger() @@ -51,9 +58,8 @@ @member_management_bp.route('/manage') def display_member_management(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display member management') + log = logger.new(request=request) + log.info('Display Member Management') username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -64,6 +70,11 @@ def display_member_management(): member_list = get_members_info() onfloor_list = get_onfloor_members() + co_op_list = [(ldap_get_member(member.uid).displayName, member.semester, member.uid) \ + for member in CurrentCoops.query.filter( + CurrentCoops.date_created > start_of_year(), + CurrentCoops.semester != "Neither")] + freshmen = FreshmanAccount.query freshmen_list = [] @@ -80,9 +91,11 @@ def display_member_management(): if settings: lockdown = settings.site_lockdown intro_form = settings.intro_form_active + accept_dues_until = settings.accept_dues_until else: lockdown = False intro_form = False + accept_dues_until = datetime.now() return render_template(request, "member_management.html", username=username, @@ -92,15 +105,15 @@ def display_member_management(): num_fresh=len(freshmen_list), num_onfloor=len(onfloor_list), freshmen=freshmen_list, + co_op=co_op_list, site_lockdown=lockdown, + accept_dues_until=accept_dues_until, intro_form=intro_form) @member_management_bp.route('/manage/settings', methods=['PUT']) def member_management_eval(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit site settings') + log = logger.new(request=request) username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -111,14 +124,14 @@ def member_management_eval(): post_data = request.get_json() if 'siteLockdown' in post_data: - logger.info('backend', action="changed site lockdown setting to %s" % post_data['siteLockdown']) + log.info('Changed Site Lockdown: {}'.format(post_data['siteLockdown'])) EvalSettings.query.update( { 'site_lockdown': post_data['siteLockdown'] }) if 'introForm' in post_data: - logger.info('backend', action="changed intro form setting to %s" % post_data['introForm']) + log.info('Changed Intro Form: {}'.format(post_data['introForm'])) EvalSettings.query.update( { 'intro_form_active': post_data['introForm'] @@ -129,11 +142,35 @@ def member_management_eval(): return jsonify({"success": True}), 200 +@member_management_bp.route('/manage/accept_dues_until', methods=['PUT']) +def member_management_financial(): + log = logger.new(request=request) + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + + if not ldap_is_financial_director(account): + return "must be financial director", 403 + + post_data = request.get_json() + + if 'acceptDuesUntil' in post_data: + date = datetime.strptime(post_data['acceptDuesUntil'], "%Y-%m-%d") + log.info('Changed Dues Accepted Until: {}'.format(date)) + EvalSettings.query.update( + { + 'accept_dues_until': date + }) + + db.session.flush() + db.session.commit() + return jsonify({"success": True}), 200 + + @member_management_bp.route('/manage/user', methods=['POST']) def member_management_adduser(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='add fid user') + log = logger.new(request=request) + username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -146,12 +183,12 @@ def member_management_adduser(): name = post_data['name'] onfloor_status = post_data['onfloor'] room_number = post_data['roomNumber'] + log.info('Create Freshman Account for {}'.format(name)) # empty room numbers should be NULL if room_number == "": room_number = None - logger.info('backend', action="add f_%s as onfloor: %s with room_number: %s" % (name, onfloor_status, room_number)) db.session.add(FreshmanAccount(name, onfloor_status, room_number)) db.session.flush() db.session.commit() @@ -162,6 +199,7 @@ def member_management_adduser(): def member_management_uploaduser(): username = request.headers.get('x-webauth-user') account = ldap_get_member(username) + log = logger.new(request=request) if not ldap_is_eval_director(account): return "must be eval director", 403 @@ -183,6 +221,7 @@ def member_management_uploaduser(): else: room_number = None + log.info('Create Freshman Account for {} via CSV Upload'.format(name)) db.session.add(FreshmanAccount(name, onfloor_status, room_number)) db.session.flush() @@ -194,9 +233,6 @@ def member_management_uploaduser(): @member_management_bp.route('/manage/user/', methods=['POST']) def member_management_edituser(uid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='edit uid user') username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -204,31 +240,33 @@ def member_management_edituser(uid): if not ldap_is_eval_director(account) and not ldap_is_financial_director(account): return "must be eval director", 403 - post_data = request.get_json() - if not uid.isdigit(): - edit_uid(uid, username, post_data) + edit_uid(uid, request) else: - edit_fid(uid, post_data) + edit_fid(uid, request) db.session.flush() db.session.commit() return jsonify({"success": True}), 200 -def edit_uid(uid, username, post_data): - +def edit_uid(uid, flask_request): + log = logger.new(request=flask_request) + post_data = flask_request.get_json() account = ldap_get_member(uid) active_member = post_data['activeMember'] + username = flask_request.headers.get('x-webauth-user') current_account = ldap_get_member(username) if ldap_is_eval_director(current_account): room_number = post_data['roomNumber'] onfloor_status = post_data['onfloorStatus'] housing_points = post_data['housingPoints'] - logger.info('backend', action="edit %s room: %s onfloor: %s housepts %s" % - (uid, post_data['roomNumber'], post_data['onfloorStatus'], - post_data['housingPoints'])) + log.info('Edit {} - Room: {} On-Floor: {} Points: {}'.format( + uid, + post_data['roomNumber'], + post_data['onfloorStatus'], + post_data['housingPoints'])) ldap_set_roomnumber(account, room_number) if onfloor_status: @@ -247,7 +285,7 @@ def edit_uid(uid, username, post_data): ldap_set_housingpoints(account, housing_points) # Only update if there's a diff - logger.info('backend', action="edit %s active: %s" % (uid, active_member)) + log.info('Set {} Active: {}'.format(uid, active_member)) if ldap_is_active(account) != active_member: if active_member: ldap_set_active(account) @@ -266,10 +304,15 @@ def edit_uid(uid, username, post_data): clear_members_cache() -def edit_fid(uid, post_data): - logger.info('backend', action="edit freshman account %s room: %s onfloor: %s eval_date: %s sig_missed %s" % - (uid, post_data['roomNumber'], post_data['onfloorStatus'], - post_data['evalDate'], post_data['sigMissed'])) +def edit_fid(uid, flask_request): + log = logger.new(request=flask_request) + post_data = flask_request.get_json() + log.info('Edit freshman-{} - Room: {} On-Floor: {} Eval: {} SigMiss: {}'.format( + uid, + post_data['roomNumber'], + post_data['onfloorStatus'], + post_data['evalDate'], + post_data['sigMissed'])) name = post_data['name'] @@ -297,9 +340,8 @@ def edit_fid(uid, post_data): @member_management_bp.route('/manage/user/', methods=['GET']) def member_management_getuserinfo(uid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='retrieve user info') + log = logger.new(request=request) + log.info('Get {}\'s Information'.format(uid)) username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -384,9 +426,8 @@ def get_hm_date(hm_id): @member_management_bp.route('/manage/user/', methods=['DELETE']) def member_management_deleteuser(fid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='edit fid user') + log = logger.new(request=request) + log.info('Delete freshman-{}'.format(fid)) username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -397,7 +438,7 @@ def member_management_deleteuser(fid): if not fid.isdigit(): return "can only delete freshman accounts", 400 - logger.info('backend', action="delete freshman account %s" % fid) + log.info('backend', action="delete freshman account %s" % fid) for fca in FreshmanCommitteeAttendance.query.filter(FreshmanCommitteeAttendance.fid == fid): db.session.delete(fca) @@ -420,9 +461,7 @@ def member_management_deleteuser(fid): # manually need to do this @member_management_bp.route('/manage/upgrade_user', methods=['POST']) def member_management_upgrade_user(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='convert fid to uid entry') + log = logger.new(request=request) username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -436,8 +475,8 @@ def member_management_upgrade_user(): uid = post_data['uid'] signatures_missed = post_data['sigsMissed'] - logger.info('backend', action="upgrade freshman-%s to %s sigsMissed: %s" % - (fid, uid, signatures_missed)) + log.info('Upgrade freshman-{} to Account: {}'.format(fid, uid)) + acct = FreshmanAccount.query.filter( FreshmanAccount.id == fid).first() @@ -461,8 +500,10 @@ def member_management_upgrade_user(): db.session.add(MemberHouseMeetingAttendance( uid, fhm.meeting_id, fhm.excuse, fhm.attendance_status)) else: - logger.info('backend', action="duplicate house meeting attendance! fid: %s, uid: %s, id: %s" % - (fid, uid, fhm.meeting_id)) + log.info('Duplicate house meeting attendance! fid: {}, uid: {}, id: {}'.format( + fid, + uid, + fhm.meeting_id)) db.session.delete(fhm) if acct.onfloor_status: @@ -482,11 +523,27 @@ def member_management_upgrade_user(): return jsonify({"success": True}), 200 +@member_management_bp.route('/manage/make_user_active', methods=['POST']) +def member_management_make_user_active(): + log = logger.new(request=request) + + uid = request.headers.get('x-webauth-user') + account = ldap_get_member(uid) + + if not ldap_is_current_student(account) or ldap_is_active(account): + return "must be current student and not active", 403 + + ldap_set_active(account) + log.info("Make user {} active".format(uid)) + + clear_members_cache() + return jsonify({"success": True}), 200 + + @member_management_bp.route('/manage/intro_project', methods=['GET']) def introductory_project(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='show introductory project management') + log = logger.new(request=request) + log.info('Display Freshmen Project Management') username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -502,9 +559,7 @@ def introductory_project(): @member_management_bp.route('/manage/intro_project', methods=['POST']) def introductory_project_submit(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit introductory project results') + log = logger.new(request=request) username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -527,7 +582,7 @@ def introductory_project_submit(): if intro_member['status'] not in ['Passed', 'Pending', 'Failed']: abort(400) - log.info('debug', action='setting status "' + intro_member['status'] + '" for ' + intro_member['uid']) + log.info('Freshmen Project {} for {}'.format(intro_member['status'], intro_member['uid'])) FreshmanEvalData.query.filter(FreshmanEvalData.uid == intro_member['uid']).update({ 'freshman_project': intro_member['status'] @@ -540,9 +595,8 @@ def introductory_project_submit(): @member_management_bp.route('/member/', methods=['GET']) def get_member(uid): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit introductory project results') + log = logger.new(request=request) + log.info('Get {}\'s Information'.format(uid)) username = request.headers.get('x-webauth-user') account = ldap_get_member(username) @@ -558,3 +612,88 @@ def get_member(uid): } return jsonify(account_dict), 200 + +@member_management_bp.route('/manage/active', methods=['DELETE']) +def clear_active_members(): + log = logger.new(request=request) + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + + if not ldap_is_eval_director(account): + return "must be eval director", 403 + # Get the active group. + members = ldap_get_active_members() + + # Clear the active group. + for account in members: + log.info('Remove {} from Active Status'.format(account.uid)) + ldap_set_inactive(account) + return jsonify({"success": True}), 200 + + +@member_management_bp.route('/manage/export_active_list', methods=['GET']) +def export_active_list(): + sio = io.StringIO() + csvw = csv.writer(sio) + + active_list = [["Full Name", "RIT Username", "Amount to Charge"]] + for member in ldap_get_active_members(): + full_name = member.cn + rit_username = re.search(".*uid=(\\w*)", member.ritDn).group(1) + will_coop = CurrentCoops.query.filter( + CurrentCoops.date_created > start_of_year(), + CurrentCoops.uid == member.uid).first() + dues_per_semester = app.config['DUES_PER_SEMESTER'] + if will_coop: + dues = dues_per_semester + else: + dues = 2 * dues_per_semester + active_list.append([full_name, rit_username, dues]) + + csvw.writerows(active_list) + output = make_response(sio.getvalue()) + output.headers["Content-Disposition"] = "attachment; filename=csh_active_list.csv" + output.headers["Content-type"] = "text/csv" + return output + + +@member_management_bp.route('/manage/current/', methods=['POST', 'DELETE']) +def remove_current_student(uid): + log = logger.new(request=request) + + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + + if not ldap_is_eval_director(account): + return "must be eval director", 403 + + member = ldap_get_member(uid) + if request.method == 'DELETE': + log.info('Remove {} from Current Student'.format(uid)) + ldap_set_non_current_student(member) + elif request.method == 'POST': + log.info('Add {} to Current Students'.format(uid)) + ldap_set_current_student(member) + return jsonify({"success": True}), 200 + + +@member_management_bp.route('/manage/new', methods=['GET']) +def new_year(): + log = logger.new(request=request) + log.info('Display New Year Page') + + username = request.headers.get('x-webauth-user') + account = ldap_get_member(username) + + if not ldap_is_eval_director(account): + return "must be eval director", 403 + + current_students = ldap_get_current_students() + + + return render_template(request, + 'new_year.html', + username=username, + current_students=current_students) diff --git a/conditional/blueprints/slideshow.py b/conditional/blueprints/slideshow.py index 5ed0055b..63e25944 100644 --- a/conditional/blueprints/slideshow.py +++ b/conditional/blueprints/slideshow.py @@ -1,5 +1,4 @@ import json -import uuid from datetime import datetime @@ -26,9 +25,8 @@ @slideshow_bp.route('/slideshow/intro') def slideshow_intro_display(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display intro slideshow') + log = logger.new(request=request) + log.info('Display Intro Slideshow') user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) @@ -45,9 +43,8 @@ def slideshow_intro_display(): @slideshow_bp.route('/slideshow/intro/members') def slideshow_intro_members(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='retrieve intro members slideshow data') + log = logger.new(request=request) + log.info('Retrieve Intro Members Slideshow Data') # can't be jsonify because # ValueError: dictionary update sequence element #0 has length 7; 2 is @@ -57,9 +54,7 @@ def slideshow_intro_members(): @slideshow_bp.route('/slideshow/intro/review', methods=['POST']) def slideshow_intro_review(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit intro member evaluation') + log = logger.new(request=request) # get user data user_name = request.headers.get('x-webauth-user') @@ -72,7 +67,7 @@ def slideshow_intro_review(): uid = post_data['uid'] status = post_data['status'] - logger.info("backend", action="submit intro eval for %s status: %s" % (uid, status)) + log.info('Intro Eval for {}: {}'.format(uid, status)) FreshmanEvalData.query.filter( FreshmanEvalData.uid == uid and FreshmanEvalData.active). \ @@ -88,9 +83,8 @@ def slideshow_intro_review(): @slideshow_bp.route('/slideshow/spring') def slideshow_spring_display(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display membership evaluations slideshow') + log = logger.new(request=request) + log.info('Display Membership Evaluations Slideshow') user_name = request.headers.get('x-webauth-user') account = ldap_get_member(user_name) @@ -107,9 +101,8 @@ def slideshow_spring_display(): @slideshow_bp.route('/slideshow/spring/members') def slideshow_spring_members(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='retreive membership evaluations slideshow daat') + log = logger.new(request=request) + log.info('Retreive Membership Evaluations Slideshow Data') # can't be jsonify because # ValueError: dictionary update sequence element #0 has length 7; 2 is @@ -119,9 +112,7 @@ def slideshow_spring_members(): @slideshow_bp.route('/slideshow/spring/review', methods=['POST']) def slideshow_spring_review(): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('api', action='submit membership evaulation') + log = logger.new(request=request) # get user data user_name = request.headers.get('x-webauth-user') @@ -134,7 +125,7 @@ def slideshow_spring_review(): uid = post_data['uid'] status = post_data['status'] - logger.info("backend", action="submit spring eval for %s status: %s" % (uid, status)) + log.info('Spring Eval for {}: {}'.format(uid, status)) SpringEval.query.filter( SpringEval.uid == uid and diff --git a/conditional/blueprints/spring_evals.py b/conditional/blueprints/spring_evals.py index 6931bab6..eb0eabe5 100644 --- a/conditional/blueprints/spring_evals.py +++ b/conditional/blueprints/spring_evals.py @@ -1,12 +1,9 @@ -import uuid import structlog from flask import Blueprint, request from conditional.util.ldap import ldap_get_active_members -from conditional.models.models import MemberCommitteeAttendance -from conditional.models.models import CommitteeMeeting from conditional.models.models import MemberHouseMeetingAttendance from conditional.models.models import MajorProject from conditional.models.models import HouseMeeting @@ -14,7 +11,9 @@ from conditional.util.flask import render_template -from conditional import db +from conditional.util.member import get_cm, get_hm + +from conditional import db, start_of_year spring_evals_bp = Blueprint('spring_evals_bp', __name__) @@ -23,14 +22,8 @@ @spring_evals_bp.route('/spring_evals/') def display_spring_evals(internal=False): - log = logger.new(user_name=request.headers.get("x-webauth-user"), - request_id=str(uuid.uuid4())) - log.info('frontend', action='display membership evaluations listing') - - def get_cm_count(member_id): - return len([a for a in MemberCommitteeAttendance.query.filter( - MemberCommitteeAttendance.uid == member_id) - if CommitteeMeeting.query.filter(CommitteeMeeting.id == a.meeting_id).approved]) + log = logger.new(request=request) + log.info('Display Membership Evaluations Listing') user_name = None if not internal: @@ -42,31 +35,26 @@ def get_cm_count(member_id): for account in active_members: uid = account.uid spring_entry = SpringEval.query.filter( - SpringEval.uid == uid and - SpringEval.active).first() + SpringEval.date_created > start_of_year(), + SpringEval.uid == uid, + SpringEval.active == True).first() # pylint: disable=singleton-comparison if spring_entry is None: spring_entry = SpringEval(uid) db.session.add(spring_entry) db.session.flush() db.session.commit() - # something bad happened to get here... elif spring_entry.status != "Pending" and internal: continue eval_data = None - h_meetings = [m.meeting_id for m in - MemberHouseMeetingAttendance.query.filter( - MemberHouseMeetingAttendance.uid == uid - ).filter( - MemberHouseMeetingAttendance.attendance_status == "Absent" - )] + h_meetings = [m.meeting_id for m in get_hm(account)] member = { 'name': account.cn, 'uid': uid, 'status': spring_entry.status, - 'committee_meetings': get_cm_count(uid), + 'committee_meetings': len(get_cm(account)), 'house_meetings_missed': [ { @@ -86,16 +74,19 @@ def get_cm_count(member_id): 'status': p.status, 'description': p.description } for p in MajorProject.query.filter( + MajorProject.date > start_of_year(), MajorProject.uid == uid)] } member['major_projects_len'] = len(member['major_projects']) - member['major_project_passed'] = [ + member['major_projects_passed'] = [ { 'name': p.name, 'status': p.status, 'description': p.description - } for p in MajorProject.query.filter(MajorProject.uid == uid) - if p.status == "Passed"] + } for p in MajorProject.query.filter( + MajorProject.date > start_of_year(), + MajorProject.status == "Passed", + MajorProject.uid == uid)] member['major_projects_passed_len'] = len(member['major_projects_passed']) member['major_project_passed'] = False for mp in member['major_projects']: diff --git a/conditional/models/models.py b/conditional/models/models.py index 045614a5..cd4d4563 100644 --- a/conditional/models/models.py +++ b/conditional/models/models.py @@ -2,6 +2,7 @@ from datetime import date, timedelta, datetime from sqlalchemy import Column, Integer, String, Enum, ForeignKey, DateTime, \ Date, Text, Boolean +from sqlalchemy.dialects import postgresql from conditional import db attendance_enum = Enum('Attended', 'Excused', 'Absent', name='attendance_enum') @@ -128,6 +129,7 @@ def __init__(self, fid, seminar_id): class MajorProject(db.Model): __tablename__ = 'major_projects' id = Column(Integer, primary_key=True) + date = Column(Date, nullable=False) uid = Column(String(32), nullable=False) name = Column(String(64), nullable=False) description = Column(Text) @@ -138,6 +140,7 @@ class MajorProject(db.Model): def __init__(self, uid, name, desc): self.uid = uid + self.date = datetime.now() self.name = name self.description = desc self.status = 'Pending' @@ -189,13 +192,14 @@ class CurrentCoops(db.Model): __tablename__ = 'current_coops' id = Column(Integer, primary_key=True) uid = Column(String(32), nullable=False) - active = Column(Boolean, nullable=False) date_created = Column(Date, nullable=False) + semester = Column(Enum('Fall', 'Spring', name="co_op_enum"), nullable=False) - def __init__(self, uid): + def __init__(self, uid, semester): self.uid = uid self.active = True self.date_created = datetime.now() + self.semester = semester class OnFloorStatusAssigned(db.Model): @@ -219,14 +223,18 @@ class Conditional(db.Model): status = Column(Enum('Pending', 'Passed', 'Failed', name="conditional_enum"), nullable=False) + s_evaluation = Column(ForeignKey('spring_evals.id')) + i_evaluation = Column(ForeignKey('freshman_eval_data.id')) - def __init__(self, uid, description, due): + def __init__(self, uid, description, due, s_eval=None, i_eval=None): self.uid = uid self.description = description self.date_due = due self.date_created = datetime.now() self.status = "Pending" self.active = True + self.s_evaluation = s_eval + self.i_evaluation = i_eval class EvalSettings(db.Model): @@ -235,11 +243,13 @@ class EvalSettings(db.Model): housing_form_active = Column(Boolean) intro_form_active = Column(Boolean) site_lockdown = Column(Boolean) + accept_dues_until = Column(Date) def __init__(self): self.housing_form_active = True self.intro_form_active = True self.site_lockdown = False + self.accept_dues_until = datetime.now() class SpringEval(db.Model): @@ -262,3 +272,25 @@ def __init__(self, uid): class InHousingQueue(db.Model): __tablename__ = 'in_housing_queue' uid = Column(String(32), primary_key=True) + +http_enum = Enum('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH', name='http_enum') + +class UserLog(db.Model): + __tablename__ = 'user_log' + id = Column(Integer, primary_key=True) + ipaddr = Column(postgresql.INET, nullable=False) + timestamp = Column(DateTime, nullable=False) + uid = Column(String(32), nullable=False) + method = Column(http_enum) + blueprint = Column(String(32), nullable=False) + path = Column(String(128), nullable=False) + description = Column(String(128), nullable=False) + + def __init__(self, ipaddr, user, method, blueprint, path, description): + self.ipaddr = ipaddr + self.timestamp = datetime.now() + self.uid = user + self.method = method + self.blueprint = blueprint + self.path = path + self.description = description diff --git a/conditional/templates/co_op.html b/conditional/templates/co_op.html new file mode 100644 index 00000000..9be54673 --- /dev/null +++ b/conditional/templates/co_op.html @@ -0,0 +1,51 @@ +{% extends "nav.html" %} +{% block title %} +Co-Op Submission +{% endblock %} +{% block body %} +
+

Co-Op Submission

+
+
+
+
+ {% if not on_coop %} +

Please select one of the following options below if you are going to be on co-op during either semester this year. In the case you are going to be on co-op for both semesters, please contact the Evaluations Director and have them remove you from Active status.

+

By selecting an option and submitting this form you are forfeiting your right to vote in house matters during the selected period. You are not required to attend house meetings but are still eligable for directorship meeting attendance.

+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+ {% else %} +

Thank you for submitting this form. Good luck on your {{on_coop.semester}} co-op!

+

If you submitted this form by mistake or your plans have changed, please contact the Evaluations Director.

+ {% endif %} +
+
+
+
+
+{% endblock %} diff --git a/conditional/templates/dashboard.html b/conditional/templates/dashboard.html index e2cc29ab..dee32941 100644 --- a/conditional/templates/dashboard.html +++ b/conditional/templates/dashboard.html @@ -251,7 +251,22 @@

Major Projects

{% endif %} + {% if accepting_dues and student and not active %} +
+
+

Become Active

+
+
+ Hey there, you're eligible to become an active member! Click the button below if you'd like to become active and pay dues. +
+ +
+ {% endif %} + +
{% if housing %}
diff --git a/conditional/templates/logs.html b/conditional/templates/logs.html new file mode 100644 index 00000000..0b6323b5 --- /dev/null +++ b/conditional/templates/logs.html @@ -0,0 +1,36 @@ +{% extends "nav.html" %} +{% block title %} +User Logs +{% endblock %} +{% block body %} + +
+

User Logs

+
+
+ + + + + + + + + + + {% for log in logs %} + + + + + + + + + {% endfor %} + +
IDMethodUserBlueprintURLDescription
{{log.timestamp}}{{log.method}}{{log.uid}}{{log.blueprint}}{{log.path}}{{log.description}}
+
+
+
+{% endblock %} diff --git a/conditional/templates/member_management.html b/conditional/templates/member_management.html index 355a159d..6a96d5de 100644 --- a/conditional/templates/member_management.html +++ b/conditional/templates/member_management.html @@ -10,7 +10,7 @@

Administration

-
+
{{num_current}}
Current Students
@@ -27,17 +27,62 @@

Administration

Intro Accounts
{% if is_eval_director %} -
+
Site Lockdown
+ + {% else %} +
+ + +
{% endif %}
+ {% if co_op and is_eval_director%} +
+
+

Co-Op Management

+
+
+
+ + + + + + + + + + {% for c in co_op %} + + + + + + {% endfor %} + +
NameSemesterDelete
+ {{c[0]}} + + {{c[1]}} + + + + +
+
+
+
+ {% endif %} {% if is_eval_director %}
@@ -126,7 +171,14 @@

Freshmen Management

-

Member Management

+

+ Member Management + + + +

diff --git a/conditional/templates/nav.html b/conditional/templates/nav.html index 2d926f15..d84557d5 100644 --- a/conditional/templates/nav.html +++ b/conditional/templates/nav.html @@ -25,6 +25,7 @@
{% set major_projects_passed = m['major_projects_passed_len'] >= 1 %} -
- +
+

{{m['major_projects_passed_len']}}

Major Projects

diff --git a/conditional/util/flask.py b/conditional/util/flask.py index 4d1e356f..19534743 100644 --- a/conditional/util/flask.py +++ b/conditional/util/flask.py @@ -1,3 +1,5 @@ +from datetime import date + from flask import render_template as flask_render_template from conditional.models.models import EvalSettings @@ -27,6 +29,7 @@ def render_template(request, template_name, **kwargs): db.session.commit() account = ldap_get_member(user_name) lockdown = EvalSettings.query.first().site_lockdown + accepting_dues = EvalSettings.query.first().accept_dues_until > date.today() is_active = ldap_is_active(account) is_alumni = ldap_is_alumni(account) is_eboard = ldap_is_eboard(account) @@ -46,6 +49,7 @@ def render_template(request, template_name, **kwargs): return flask_render_template( template_name, lockdown=lockdown, + accepting_dues=accepting_dues, is_active=is_active, is_alumni=is_alumni, is_eboard=is_eboard, diff --git a/conditional/util/ldap.py b/conditional/util/ldap.py index cbe140b1..443c877d 100644 --- a/conditional/util/ldap.py +++ b/conditional/util/ldap.py @@ -121,6 +121,18 @@ def ldap_set_inactive(account): ldap_get_member.cache_clear() +def ldap_set_current_student(account): + _ldap_add_member_to_group(account, 'current_student') + ldap_get_current_students.cache_clear() + ldap_get_member.cache_clear() + + +def ldap_set_non_current_student(account): + _ldap_remove_member_from_group(account, 'current_student') + ldap_get_current_students.cache_clear() + ldap_get_member.cache_clear() + + def ldap_get_roomnumber(account): try: return account.roomNumber diff --git a/conditional/util/member.py b/conditional/util/member.py index d664e524..23f7e61c 100644 --- a/conditional/util/member.py +++ b/conditional/util/member.py @@ -1,4 +1,5 @@ from functools import lru_cache +from datetime import datetime from conditional.util.ldap import ldap_get_active_members from conditional.util.ldap import ldap_get_intro_members @@ -14,19 +15,35 @@ from conditional.models.models import MemberHouseMeetingAttendance from conditional.models.models import MemberCommitteeAttendance from conditional.models.models import TechnicalSeminar +from conditional.models.models import HouseMeeting +from conditional.models.models import CurrentCoops + +from conditional import start_of_year @lru_cache(maxsize=1024) def get_voting_members(): - voting_list = [uid for uid in [member.uid for member in ldap_get_active_members()] - if uid not in [member.uid for member in ldap_get_intro_members()]] + + if datetime.today() < datetime(start_of_year().year, 12, 31): + semester = 'Fall' + else: + semester = 'Spring' + + active_members = set(member.uid for member in ldap_get_active_members()) + intro_members = set(member.uid for member in ldap_get_intro_members()) + on_coop = set(member.uid for member in CurrentCoops.query.filter( + CurrentCoops.date_created > start_of_year(), + CurrentCoops.semester == semester).all()) + + voting_list = list(active_members - intro_members - on_coop) passed_fall = FreshmanEvalData.query.filter( FreshmanEvalData.freshman_eval_result == "Passed" ).distinct() for intro_member in passed_fall: - voting_list.append(intro_member.uid) + if intro_member.uid not in voting_list: + voting_list.append(intro_member.uid) return voting_list @@ -96,3 +113,28 @@ def get_freshman_data(user_name): def get_onfloor_members(): return [uid for uid in [members.uid for members in ldap_get_active_members()] if uid in [members.uid for members in ldap_get_onfloor_members()]] + + +def get_cm(member): + try: + c_meetings = [m.meeting_id for m in + MemberCommitteeAttendance.query.filter( + MemberCommitteeAttendance.uid == member.uid + ) if CommitteeMeeting.query.filter( + CommitteeMeeting.timestamp > start_of_year(), + CommitteeMeeting.id == m.meeting_id).first().approved] + except AttributeError: + c_meetings = [] + return c_meetings + + +def get_hm(member): + h_meetings = MemberHouseMeetingAttendance.query.outerjoin( + HouseMeeting, + MemberHouseMeetingAttendance.meeting_id == HouseMeeting.id).with_entities( + MemberHouseMeetingAttendance.meeting_id, + MemberHouseMeetingAttendance.attendance_status, + HouseMeeting.date).filter( + HouseMeeting.date > start_of_year(), + MemberHouseMeetingAttendance.uid == member.uid) + return h_meetings diff --git a/config.sample.py b/config.sample.py index 49eaa16a..4fc12ba4 100644 --- a/config.sample.py +++ b/config.sample.py @@ -24,3 +24,6 @@ # Database config SQLALCHEMY_DATABASE_URI = 'sqlite:///{}'.format(os.path.join(os.getcwd(), "data.db")) ZOO_DATABASE_URI = 'mysql+pymysql://user:pass@host/database' + +# General config +DUES_PER_SEMESTER = 80 diff --git a/frontend/javascript/modules/acceptDuesDatepicker.js b/frontend/javascript/modules/acceptDuesDatepicker.js new file mode 100644 index 00000000..b83d9905 --- /dev/null +++ b/frontend/javascript/modules/acceptDuesDatepicker.js @@ -0,0 +1,58 @@ +/* global $ */ +import "bootstrap-material-datetimepicker"; +import "whatwg-fetch"; +import FetchUtil from "../utils/fetchUtil"; +import Exception from "../exceptions/exception"; +import FetchException from "../exceptions/fetchException"; +import sweetAlert from "../../../node_modules/bootstrap-sweetalert/dev/sweetalert.es6.js"; // eslint-disable-line max-len + +export default class DatePicker { + constructor(input) { + this.input = input; + this.endpoint = '/manage/accept_dues_until'; + this.setting = input.dataset.setting; + this.render(); + } + + render() { + $(this.input).bootstrapMaterialDatePicker({ + weekStart: 0, + time: false + }); + + document.getElementsByClassName('dtp-btn-ok')[0].addEventListener('click', + () => { + this._updateSetting(); + }); + } + + _updateSetting() { + console.log("Update dues until: " + this.input.value); + let payload = {}; + payload[this.setting] = this.input.value; + + fetch(this.endpoint, { + method: 'PUT', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + credentials: 'same-origin', + body: JSON.stringify(payload) + }) + .then(FetchUtil.checkStatus) + .then(FetchUtil.parseJSON) + .then(response => { + if (!response.hasOwnProperty('success') || !response.success) { + sweetAlert("Uh oh...", "We're having trouble submitting this " + + "form right now. Please try again later.", "error"); + throw new Exception(FetchException.REQUEST_FAILED, response); + } + }) + .catch(error => { + sweetAlert("Uh oh...", "We're having trouble submitting this " + + "form right now. Please try again later.", "error"); + throw new Exception(FetchException.REQUEST_FAILED, error); + }); + } +} diff --git a/frontend/javascript/modules/becomeActive.js b/frontend/javascript/modules/becomeActive.js new file mode 100644 index 00000000..4de6d1f0 --- /dev/null +++ b/frontend/javascript/modules/becomeActive.js @@ -0,0 +1,26 @@ +import FetchUtil from "../utils/fetchUtil"; + +export default class becomeActive { + constructor(link) { + this.link = link; + this.endpoint = '/manage/make_user_active'; + this.render(); + } + + render() { + this.link.addEventListener('click', e => this._delete(e)); + } + + _delete(e) { + e.preventDefault(); + + FetchUtil.postWithWarning(this.endpoint, {}, { + warningText: "Becoming an active member means that you will be charged" + + " dues, which are detailed in the CSH Constitution.", + successText: "You are now an active member." + }, () => { + document.getElementById('becomeActive').remove(); + }); + } +} + diff --git a/frontend/javascript/modules/conditionalActions.js b/frontend/javascript/modules/conditionalActions.js index 541d9bd9..d71a8338 100644 --- a/frontend/javascript/modules/conditionalActions.js +++ b/frontend/javascript/modules/conditionalActions.js @@ -28,7 +28,6 @@ export default class ConditionalActions { }); } else { const actionExt = (action === "pass") ? "Passed" : "Failed"; - let payload = { id: this.id, status: actionExt diff --git a/frontend/javascript/modules/conditionalForm.js b/frontend/javascript/modules/conditionalForm.js index dcf6ab60..8cd4ad89 100644 --- a/frontend/javascript/modules/conditionalForm.js +++ b/frontend/javascript/modules/conditionalForm.js @@ -16,10 +16,15 @@ export default class ConditionalForm { _submitForm(e) { e.preventDefault(); let uid = this.form.uid.value; + let evaluation = null; + if (location.pathname.split('/')[1] === "slideshow") { + evaluation = location.pathname.split('/')[2]; + } let payload = { uid: uid, description: this.form.querySelector('input[name=description]').value, - dueDate: this.form.querySelector('input[name=due_date]').value + dueDate: this.form.querySelector('input[name=due_date]').value, + evaluation: evaluation }; FetchUtil.postWithWarning(this.endpoint, payload, { warningText: "Are you sure you want to create this conditional?", diff --git a/frontend/javascript/modules/coopDelete.js b/frontend/javascript/modules/coopDelete.js new file mode 100644 index 00000000..cb944da0 --- /dev/null +++ b/frontend/javascript/modules/coopDelete.js @@ -0,0 +1,27 @@ +import FetchUtil from "../utils/fetchUtil"; + +export default class coopDelete { + constructor(link) { + this.link = link; + this.uid = this.link.dataset.uid; + this.endpoint = '/co_op/' + this.uid; + this.render(); + } + + render() { + this.link.addEventListener('click', e => this._delete(e)); + } + + _delete(e) { + e.preventDefault(); + + FetchUtil.fetchWithWarning(this.endpoint, { + method: 'DELETE', + warningText: "This co-op entry will be deleted and the user will no" + + " longer be excluded from attendance and vote counts.", + successText: "Co-op has been deleted." + }, () => { + document.getElementById('coop-' + this.uid).remove(); + }); + } +} diff --git a/frontend/javascript/modules/coopSubmitForm.js b/frontend/javascript/modules/coopSubmitForm.js new file mode 100644 index 00000000..493a1b02 --- /dev/null +++ b/frontend/javascript/modules/coopSubmitForm.js @@ -0,0 +1,28 @@ +import FetchUtil from "../utils/fetchUtil"; + +export default class coopSubmitForm { + constructor(form) { + this.form = form; + this.endpoint = '/co_op/submit'; + this.render(); + } + + render() { + this.form.querySelector('input[type=submit]') + .addEventListener('click', e => this._submitForm(e)); + } + + _submitForm(e) { + e.preventDefault(); + + let payload = { + semester: this.form.querySelector('input[name=semester]:checked').value + }; + + FetchUtil.postWithWarning(this.endpoint, payload, { + warningText: "You will not be able to edit your " + + "submission once it is submitted!", + successText: "Your co-op has been submitted." + }); + } +} diff --git a/frontend/javascript/modules/newYear.js b/frontend/javascript/modules/newYear.js new file mode 100644 index 00000000..0d8ba6ae --- /dev/null +++ b/frontend/javascript/modules/newYear.js @@ -0,0 +1,80 @@ +import FetchUtil from '../utils/fetchUtil'; +import Exception from "../exceptions/exception"; +import FetchException from "../exceptions/fetchException"; +import sweetAlert from "../../../node_modules/bootstrap-sweetalert/dev/sweetalert.es6.js"; // eslint-disable-line max-len + +export default class NewYear { + constructor(link) { + this.link = link; + this.step = this.link.dataset.step; + this.uid = this.link.dataset.uid; + + this.endpoints = { + housing: '/housing', + active: '/manage/active', + current: '/manage/current/' + }; + + this.render(); + } + render() { + this.link.addEventListener('click', e => { + e.preventDefault(); + + if (this.step === "welcome") { + $('#new-welcome').fadeOut(() => { + $("#new-clear").fadeIn(); + }); + } else if (this.step === "clear") { + FetchUtil.fetchWithWarning(this.endpoints.active, { + method: 'DELETE', + warningText: "This will clear active members and room assignments!", + successText: "Data successfully cleared."}, () => { + fetch(this.endpoints.housing, { + method: 'DELETE' + }) + .then($('#new-clear').fadeOut(() => { + $("#new-current").fadeIn(); + }) + ).catch(error => { + sweetAlert("Uh oh...", "We're having trouble submitting that " + + "action right now. Please try again later.", "error"); + throw new Exception(FetchException.REQUEST_FAILED, error); + }); + }); + } else if (this.uid) { + if ($('#rem-' + this.uid).is(":visible")) { + fetch(this.endpoints.current + this.uid, { + method: 'DELETE' + }).then(() => { + $('#rem-' + this.uid).hide(); + $('#add-' + this.uid).show(); + var userRow = $('#row-' + this.uid)[0]; + userRow.style.setProperty("text-decoration", "line-through"); + }).catch(error => { + sweetAlert("Uh oh...", "We're having trouble submitting that " + + "action right now. Please try again later.", "error"); + throw new Exception(FetchException.REQUEST_FAILED, error); + }); + } else { + fetch(this.endpoints.current + this.uid, { + method: 'POST' + }).then(() => { + $('#add-' + this.uid).hide(); + $('#rem-' + this.uid).show(); + var lineRow = $('#row-' + this.uid)[0]; + lineRow.style.setProperty("text-decoration", "none"); + }).catch(error => { + sweetAlert("Uh oh...", "We're having trouble submitting that " + + "action right now. Please try again later.", "error"); + throw new Exception(FetchException.REQUEST_FAILED, error); + }); + } + } else if (this.step === "current") { + $('#new-current').fadeOut(function() { + $("#new-housing").fadeIn(); + }); + } + }); + } +} diff --git a/frontend/javascript/modules/table.js b/frontend/javascript/modules/table.js index 799e5ce4..e1478820 100644 --- a/frontend/javascript/modules/table.js +++ b/frontend/javascript/modules/table.js @@ -17,7 +17,8 @@ export default class Table { info: false, paging: !(this.table.dataset.paginated === 'false'), pagingType: "numbers", - order: [] + order: [], + pageLength: this.table.dataset.pageLength || 10 }; // Just remove the search input from the DOM instead of disabling it diff --git a/frontend/stylesheets/app.scss b/frontend/stylesheets/app.scss index 6df7c498..c956d253 100644 --- a/frontend/stylesheets/app.scss +++ b/frontend/stylesheets/app.scss @@ -27,4 +27,5 @@ @import 'pages/hm-attendance'; @import 'pages/attendance-hist'; @import 'pages/errors'; +@import 'pages/co-op'; @import 'components/reveal'; diff --git a/frontend/stylesheets/pages/_co-op.scss b/frontend/stylesheets/pages/_co-op.scss new file mode 100644 index 00000000..6da382c8 --- /dev/null +++ b/frontend/stylesheets/pages/_co-op.scss @@ -0,0 +1,20 @@ +.co-op-description { + margin-bottom: 25px; +} + +.co-op-group { + margin-top: 25px; +} + +.co-op-option { + padding-left: 10px; +} + +.co-op-radio { + input { + &[type='radio'] { + position: fixed; + margin-top: 20px; + } + } +} diff --git a/frontend/stylesheets/pages/_management.scss b/frontend/stylesheets/pages/_management.scss index f92d0a9c..ba5d2d07 100644 --- a/frontend/stylesheets/pages/_management.scss +++ b/frontend/stylesheets/pages/_management.scss @@ -1,9 +1,28 @@ .switch-label { - display: inline-block; - padding: 40px 10px; + display: block; + padding: 17px 10px 10px; text-align: center; } +.btn-new-year { + margin-top: 30px; +} + +.btn-new-next { + margin: 30px 0; +} + +.btn-get-active { + float: right; + margin-top: -4px; + box-shadow: none; + padding: 3px 7px; +} + +.accept-dues-until { + margin-top: 10px; +} + .upload-title { padding-top: 20px; height: 55px; diff --git a/frontend/stylesheets/partials/_global.scss b/frontend/stylesheets/partials/_global.scss index d9907cae..ec9be224 100644 --- a/frontend/stylesheets/partials/_global.scss +++ b/frontend/stylesheets/partials/_global.scss @@ -1,4 +1,5 @@ -.navbar-fixed-bottom, .navbar-fixed-top { +.navbar-fixed-bottom, +.navbar-fixed-top { z-index: 900; } @@ -87,3 +88,7 @@ tr { float: none; vertical-align: middle; } + +.hidden { + display: none; +} diff --git a/migrations/versions/117567def844_.py b/migrations/versions/117567def844_.py new file mode 100644 index 00000000..58f8361c --- /dev/null +++ b/migrations/versions/117567def844_.py @@ -0,0 +1,36 @@ +"""Add Log Table for User Actions + +Revision ID: 117567def844 +Revises: 2c3193839c9d +Create Date: 2017-06-19 00:39:17.408944 + +""" + +# revision identifiers, used by Alembic. +revision = '117567def844' +down_revision = '2c3193839c9d' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.create_table('user_log', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('ipaddr', postgresql.INET(), nullable=False), + sa.Column('timestamp', sa.DateTime(), nullable=False), + sa.Column('uid', sa.String(length=32), nullable=False), + sa.Column('method', sa.Enum('GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'CONNECT', 'OPTIONS', 'TRACE', 'PATCH', name='http_enum'), nullable=True), + sa.Column('blueprint', sa.String(length=32), nullable=False), + sa.Column('path', sa.String(length=128), nullable=False), + sa.Column('description', sa.String(length=128), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_table('user_log') + ### end Alembic commands ### diff --git a/migrations/versions/2c3193839c9d_.py b/migrations/versions/2c3193839c9d_.py new file mode 100644 index 00000000..f322cc0c --- /dev/null +++ b/migrations/versions/2c3193839c9d_.py @@ -0,0 +1,31 @@ +"""Add Semester Enum to Co-Op Table + +Revision ID: 2c3193839c9d +Revises: 6ae578b76143 +Create Date: 2017-05-24 00:18:22.645256 + +""" + +# revision identifiers, used by Alembic. +revision = '2c3193839c9d' +down_revision = '6ae578b76143' + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + semester = postgresql.ENUM('Fall', 'Spring', 'Neither', name='co_op_enum') + semester.create(op.get_bind()) + op.add_column('current_coops', sa.Column('semester', sa.Enum('Fall', 'Spring', 'Neither', name='co_op_enum'), server_default='Neither', nullable=False)) + op.drop_column('current_coops', 'active') + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('current_coops', sa.Column('active', sa.BOOLEAN(), server_default=sa.sql.expression.true(), autoincrement=False, nullable=False)) + op.drop_column('current_coops', 'semester') + ### end Alembic commands ### diff --git a/migrations/versions/5615d58892a1_.py b/migrations/versions/5615d58892a1_.py new file mode 100644 index 00000000..c92603d1 --- /dev/null +++ b/migrations/versions/5615d58892a1_.py @@ -0,0 +1,36 @@ +"""Allow for Spring and Intro eval relationships + +Revision ID: 5615d58892a1 +Revises: 6f6b843e2b8f +Create Date: 2017-04-27 15:40:40.640402 + +""" + +# revision identifiers, used by Alembic. +revision = '5615d58892a1' +down_revision = '6f6b843e2b8f' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('conditional', sa.Column('i_evaluation', sa.Integer(), nullable=True)) + op.add_column('conditional', sa.Column('s_evaluation', sa.Integer(), nullable=True)) + op.drop_constraint('conditional_evaluation_fkey', 'conditional', type_='foreignkey') + op.create_foreign_key(None, 'conditional', 'spring_evals', ['s_evaluation'], ['id']) + op.create_foreign_key(None, 'conditional', 'freshman_eval_data', ['i_evaluation'], ['id']) + op.drop_column('conditional', 'evaluation') + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('conditional', sa.Column('evaluation', sa.INTEGER(), autoincrement=False, nullable=True)) + op.drop_constraint(None, 'conditional', type_='foreignkey') + op.drop_constraint(None, 'conditional', type_='foreignkey') + op.create_foreign_key('conditional_evaluation_fkey', 'conditional', 'spring_evals', ['evaluation'], ['id']) + op.drop_column('conditional', 's_evaluation') + op.drop_column('conditional', 'i_evaluation') + ### end Alembic commands ### diff --git a/migrations/versions/6ae578b76143_.py b/migrations/versions/6ae578b76143_.py new file mode 100644 index 00000000..b2afb069 --- /dev/null +++ b/migrations/versions/6ae578b76143_.py @@ -0,0 +1,27 @@ +"""Add Date to Submitted Major Projects + +Revision ID: 6ae578b76143 +Revises: 5615d58892a1 +Create Date: 2017-05-21 22:59:23.917438 + +""" + +# revision identifiers, used by Alembic. +revision = '6ae578b76143' +down_revision = '5615d58892a1' + +from alembic import op +from datetime import datetime +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('major_projects', sa.Column('date', sa.Date(), server_default=datetime.now().strftime("%Y-%m-%d"), nullable=False)) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_column('major_projects', 'date') + ### end Alembic commands ### diff --git a/migrations/versions/6f6b843e2b8f_.py b/migrations/versions/6f6b843e2b8f_.py new file mode 100644 index 00000000..cef721db --- /dev/null +++ b/migrations/versions/6f6b843e2b8f_.py @@ -0,0 +1,28 @@ +"""Add relation between Conditional and Eval + +Revision ID: 6f6b843e2b8f +Revises: e72e5d4d71f2 +Create Date: 2017-04-26 19:44:14.159135 + +""" + +# revision identifiers, used by Alembic. +revision = '6f6b843e2b8f' +down_revision = 'e72e5d4d71f2' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('conditional', sa.Column('evaluation', sa.Integer(), nullable=True)) + op.create_foreign_key(None, 'conditional', 'spring_evals', ['evaluation'], ['id']) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, 'conditional', type_='foreignkey') + op.drop_column('conditional', 'evaluation') + ### end Alembic commands ### diff --git a/migrations/versions/d1a06ab54211_.py b/migrations/versions/d1a06ab54211_.py new file mode 100644 index 00000000..e0e3c41b --- /dev/null +++ b/migrations/versions/d1a06ab54211_.py @@ -0,0 +1,26 @@ +"""Add Accept Dues Until site setting + +Revision ID: d1a06ab54211 +Revises: 117567def844 +Create Date: 2017-07-21 17:09:37.540766 + +""" + +# revision identifiers, used by Alembic. +revision = 'd1a06ab54211' +down_revision = '117567def844' + +from alembic import op +import sqlalchemy as sa +from datetime import datetime + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('settings', sa.Column('accept_dues_until', sa.Date(), server_default=datetime.now().strftime("%Y-%m-%d"), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('settings', 'accept_dues_until') + # ### end Alembic commands ###