diff --git a/README.md b/README.md index 8e33388..a6b4f76 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,24 @@ ## Worflow +install the dependencies by running `pip install -r requirements.txt` start the server by running `$python server.py` ### Login -`$http --session=./session.json -f POST http://127.0.0.1:5000/api/login username=user@example.com password=password` +`$ http --session=./session.json -f POST http://127.0.0.1:5000/api/login username=user@example.com password=password` ### Update Attendance -Student : -`$http --session=./session.json -f POST http://127.0.0.1:5000/api/attendance/student presence=absent identifier=2 time=now` -Class : -`http --session=./session.json -f POST http://127.0.0.1:5000/api/attendance/class presence=present identifier=1 time=now` +#### Student : +`$ http --session=./session.json -f POST http://127.0.0.1:5000/api/attendance/student presence=absent identifier=2 time=now` +#### Class : +`$ http --session=./session.json -f POST http://127.0.0.1:5000/api/attendance/class presence=present identifier=1 time=now` ### Get Attendance -Student: -`http --session=./session.json GET "http://127.0.0.1:5000/api/attendance/student?identifier=2&start_time=now&end_time=now"` -`http --session=./session.json GET "http://127.0.0.1:5000/api/attendance/student?identifier=3&start_time=now&end_time=now"` -Class: -`http --session=./session.json GET "http://127.0.0.1:5000/api/attendance/class?identifier=1&start_time=now&end_time=now"` +#### Student: +`$ http --session=./session.json GET "http://127.0.0.1:5000/api/attendance/student?identifier=2&start_time=now&end_time=now"` + +or + +`$ http --session=./session.json GET "http://127.0.0.1:5000/api/attendance/student?identifier=3&start_time=now&end_time=now"` +#### Class: +`$ http --session=./session.json GET "http://127.0.0.1:5000/api/attendance/class?identifier=1&start_time=now&end_time=now"` diff --git a/models.py b/models.py index 0480a99..99c99b5 100644 --- a/models.py +++ b/models.py @@ -35,6 +35,7 @@ class Presence(Enum): """docstring for ResultType.""" PRESENT, ABSENT, SICK, VACATION = range(4) +presense_map = {i:str(i).replace('Presence.', '').upper() for i in Presence} class Gradeclass(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -49,7 +50,7 @@ class Student(db.Model): student_name = db.Column(db.String(255)) gradeclass_id = db.Column(db.Integer, db.ForeignKey('gradeclass.id')) gradeclass = db.relationship( - 'Gradeclass', backref=db.backref('gradeclass', lazy='dynamic')) + 'Gradeclass', backref=db.backref('students', lazy='dynamic')) def __init__(self, student_name, gradeclass_id): self.student_name = student_name diff --git a/requirements.txt b/requirements.txt index 2c88971..6f099e3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,9 @@ aniso8601==1.2.1 Babel==2.5.0 bcrypt==3.1.3 blinker==1.4 +certifi==2017.7.27.1 cffi==1.10.0 +chardet==3.0.4 click==6.7 Flask==0.12.2 Flask-BabelEx==0.9.3 @@ -10,10 +12,11 @@ Flask-Login==0.4.0 Flask-Mail==0.9.1 Flask-Principal==0.4.0 Flask-Restless==0.17.0 -flask-restplus==0.10.1 Flask-Security==3.0.0 Flask-SQLAlchemy==2.2 Flask-WTF==0.14.2 +httpie==0.9.9 +idna==2.6 itsdangerous==0.24 Jinja2==2.9.6 jsonschema==2.6.0 @@ -21,11 +24,14 @@ MarkupSafe==1.0 mimerender==0.6.0 passlib==1.7.1 pycparser==2.18 +Pygments==2.2.0 python-dateutil==2.6.1 python-mimeparse==1.6.0 pytz==2017.2 +requests==2.18.4 six==1.10.0 speaklater==1.3 SQLAlchemy==1.1.13 +urllib3==1.22 Werkzeug==0.12.2 WTForms==2.1 diff --git a/server.py b/server.py index 518864d..83c3436 100644 --- a/server.py +++ b/server.py @@ -1,10 +1,11 @@ from flask import Flask, Blueprint, request from flask_security import (SQLAlchemyUserDatastore, login_required, current_user, Security) -from flask_security.utils import verify_and_update_password, login_user +from flask_security.utils import (verify_and_update_password, login_user, + logout_user) from flask_restless import APIManager, ProcessingException from models import (db, User, Role, Gradeclass, Student, AttendanceUpdate, - Presence) + Presence, presense_map) from sqlalchemy import func import json from datetime import datetime, date @@ -13,11 +14,11 @@ from dateutil import parser app = Flask(__name__) app.config['DEBUG'] = True app.config['PORT'] = 5001 -app.config['SECRET_KEY'] = 'super-secret' +app.config['SECRET_KEY'] = '3kljk1232kl1j32kmk13nbciw' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/db.sqlite' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False -app.config['DEFAULT_MAIL_SENDER'] = 'info@example.com' app.config['SECURITY_PASSWORD_SALT'] = 'uaisfyasiduyaisiuf' +app.config['SECURITY_UNAUTHORIZED_VIEW'] = None db.init_app(app) user_datastore = SQLAlchemyUserDatastore(db, User, Role) @@ -44,10 +45,10 @@ manager.create_api( results_per_page=5, preprocessors=verify_logged_id) - json_cont = {'Content-Type': 'application/json'} -resp = lambda val,code: (json.dumps({'status': val}), code, json_cont) -presense_map = {i:str(i).replace('Presence.', '').upper() for i in Presence} +resp = lambda val, code: (json.dumps({'status': val}), code, json_cont) +resp_res = lambda val, code: (json.dumps({'results': val}), code, json_cont) + @app.before_first_request def create_test_data(): @@ -59,7 +60,8 @@ def create_test_data(): db.session.add(g_cls) db.session.commit() for s in range(5): - stu = Student("TestStudent#{}-Class{}".format(s,g_cls.id), g_cls.id) + stu = Student("TestStudent#{}-Class{}".format(s + 1, g_cls.id), + g_cls.id) db.session.add(stu) user_datastore.create_user( email='user@example.com', password='password') @@ -78,26 +80,43 @@ def parse_time(time_str): return time -def compute_attendance(request_type,object_id, start_time, end_time): - def get_record(req_type,obj_id): - attend = AttendanceUpdate.query.filter( - AttendanceUpdate.update_type == req_type - ).filter( - AttendanceUpdate.value_identifier == obj_id - ).filter( - func.date(AttendanceUpdate.time) >= start_time.date() - ).filter( - func.date(AttendanceUpdate.time) >= end_time.date() - ).first() - return attend - pres_rec = None +def compute_attendance(request_type, object_id, start_time, end_time): + def get_records(req_type, obj_id): + return AttendanceUpdate.query.filter( + AttendanceUpdate.update_type == req_type).filter( + AttendanceUpdate.value_identifier == obj_id).filter( + func.date(AttendanceUpdate.time) >= + start_time.date()).filter( + func.date(AttendanceUpdate.time) >= + end_time.date()).all() + + def student_records(stud_id): + stud_recs = get_records('student', stud_id) + stud = Student.query.get(stud_id) + gcls_recs = get_records('class', stud.gradeclass_id) + pres_rec = stud_recs + for gr in gcls_recs: + # if any grade record overlaps with student record -> ignore + if not any(map(lambda x:gr.time.date()==x.time.date(),stud_recs)): + pres_rec.append(gr) + recs = [] + for r in pres_rec: + recs.append({ + "presence": presense_map[r.presence].lower(), + "student": stud.student_name, + "time": r.time.isoformat() + }) + return recs + + result = [] if request_type == 'student': - stud_rec = get_record('student',object_id) - stud = Student.query.get(object_id) - pres_rec = stud_rec if stud_rec else get_record('class',stud.gradeclass_id) + result.extend(student_records(object_id)) if request_type == 'class': - pres_rec = get_record(request_type,object_id) - return presense_map[pres_rec.presence].lower() if pres_rec else 'no_records' + gcls = Gradeclass.query.get(object_id) + for stud in gcls.students: + result.extend(student_records(stud.id)) + return result + def upsert_attendance(update_type, object_id, attend_time, pres_enum): existing = AttendanceUpdate.query.filter( @@ -115,7 +134,6 @@ def upsert_attendance(update_type, object_id, attend_time, pres_enum): def validate_attend(update_type, object_id): - # return True if update_type in ['class', 'student'] and object_id: g_cls_found = update_type == 'class' and Gradeclass.query.get( object_id) @@ -130,10 +148,10 @@ def get_attendance(update_type, request): start_timestr = request.args.get('start_time', None) end_timestr = request.args.get('end_time', None) start_time, end_time = parse_time(start_timestr), parse_time(end_timestr) - ret_data = resp('invalid',400) + ret_data = resp('invalid', 400) if validate_attend(update_type, object_id) and start_time and end_time: - pres = compute_attendance(update_type,object_id,start_time,end_time) - ret_data = resp(pres,200) + pres = compute_attendance(update_type, object_id, start_time, end_time) + ret_data = resp_res(pres, 200) return ret_data @@ -142,13 +160,14 @@ def update_attendance(update_type, request): presence = request.form.get('presence', None) attend_str = request.form.get('time', None) attend_time = parse_time(attend_str) - ret_data = resp('invalid',400) + ret_data = resp('invalid', 400) if validate_attend( - update_type, object_id - ) and presence and attend_time and presence.upper() in presense_map.values(): + update_type, + object_id) and presence and attend_time and presence.upper( + ) in presense_map.values(): pres_enum = Presence[presence.upper()] upsert_attendance(update_type, object_id, attend_time, pres_enum) - ret_data = resp('updated',200) + ret_data = resp('updated', 200) return ret_data @@ -169,13 +188,19 @@ def login(): if username and password: user = User.query.filter(User.email == username).first() if not user: - ret_data = resp('notfound',401) + ret_data = resp('notfound', 401) elif verify_and_update_password(password, user): login_user(user) - ret_data = resp('success',200) + ret_data = resp('success', 200) return ret_data +@app.route('/api/logout', methods=['GET']) +def logout(): + logout_user() + return resp('success', 200) + + @app.route('/') def home(): return 'hello'