diff --git a/backend/src/libs/cli_utils.py b/backend/src/libs/cli_utils.py index 2db22fb..3742110 100644 --- a/backend/src/libs/cli_utils.py +++ b/backend/src/libs/cli_utils.py @@ -101,13 +101,6 @@ def recognize_n_attendance(self): # store input video stream in cap variable cap = cv2.VideoCapture(self.input_video) - # find if today's attendance exists in the database - attendance = AttendanceModel.find_by_date(date=dt.today()) - # if not - if attendance is None: - # create new instance for today's attendance - attendance = AttendanceModel() - # create in dictionary for known students from database to avoid multiple queries known_students = {} @@ -163,12 +156,12 @@ def recognize_n_attendance(self): # find matched student in the database by id student = StudentModel.find_by_id(_id) known_students[_id] = student - # if student's attendance is not marked - if not attendance.is_marked(student): - # then mark student's attendance - attendance.students.append(student) - # commit changes to database - attendance.save_to_db() + # if student's attendance is not marked + if not AttendanceModel.is_marked(dt.today(), student): + # then mark student's attendance + student_attendance = AttendanceModel(student=student) + # commit changes to database + student_attendance.save_to_db() # update displayed name to student's name display_name = student.name # append the name to be displayed in names list diff --git a/backend/src/libs/web_utils.py b/backend/src/libs/web_utils.py index fc58c23..49c127f 100644 --- a/backend/src/libs/web_utils.py +++ b/backend/src/libs/web_utils.py @@ -37,22 +37,15 @@ def frames(cls): data = pickle.loads(open(ENCODINGS_FILE, "rb").read()) # print(len(data['encodings']) == len(data['ids'])) - # find if today's attendance exists in the database - attendance = AttendanceModel.find_by_date(date=dt.today()) - # if not - if attendance is None: - # create new instance for today's attendance - attendance = AttendanceModel() - # create in dictionary for known students from database to avoid multiple queries known_students = {} while True: # read current frame _, img = camera.read() - yield cls.recognize_n_attendance(img, attendance, data, known_students) + yield cls.recognize_n_attendance(img, data, known_students) @classmethod - def recognize_n_attendance(cls, frame: np.ndarray, attendance: AttendanceModel, + def recognize_n_attendance(cls, frame: np.ndarray, data: Dict, known_students: Dict) -> bytes: # convert the input frame from BGR to RGB then resize it to have # a width of 750px (to speedup processing) @@ -106,12 +99,12 @@ def recognize_n_attendance(cls, frame: np.ndarray, attendance: AttendanceModel, # find matched student in the database by id student = StudentModel.find_by_id(_id) known_students[_id] = student - # if student's attendance is not marked - if not attendance.is_marked(student): - # then mark student's attendance - attendance.students.append(student) - # commit changes to database - attendance.save_to_db() + # if student's attendance is not marked + if not AttendanceModel.is_marked(dt.today(), student): + # then mark student's attendance + student_attendance = AttendanceModel(student=student) + # commit changes to database + student_attendance.save_to_db() # update displayed name to student's name display_name = student.name # append the name to be displayed in names list diff --git a/backend/src/models.py b/backend/src/models.py index 878580d..511c7b5 100644 --- a/backend/src/models.py +++ b/backend/src/models.py @@ -2,9 +2,10 @@ from uuid import uuid4 from datetime import date as dt, datetime as dtime -from sqlalchemy import Column, Integer, String, Boolean, Date, TIMESTAMP, ForeignKey +from sqlalchemy import Column, Integer, String, Boolean, DateTime, TIMESTAMP, ForeignKey from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship, backref +from sqlalchemy.sql import func from src.db import Session @@ -37,22 +38,15 @@ def delete_from_db(self) -> None: Session.commit() -class StudentAttendances(Base): - __tablename__ = "student_attendances" - student_id = Column(Integer, ForeignKey("students.id"), primary_key=True) - date = Column(Date, ForeignKey("attendances.date"), primary_key=True) - - class StudentModel(Base): __tablename__ = "students" id = Column(Integer, primary_key=True) name = Column(String(80), unique=True, nullable=False) - # attendances = relationship( - # "AttendanceModel", - # secondary="student_attendances", - # # backref=backref("students", lazy="dynamic") - # ) + attendances = relationship( + "AttendanceModel", + backref=backref("student") + ) @classmethod def find_by_name(cls, name: str) -> "StudentModel": @@ -76,26 +70,31 @@ def delete_from_db(self) -> None: class AttendanceModel(Base): - # Many students have many attendances - # Many to Many Relationship + # One student has many attendances + # One to Many Relationship __tablename__ = "attendances" # id = Column(String(50), default=uuid4().hex, primary_key=True) - date = Column(Date, default=dt.today, primary_key=True) - # TODO: change time to show each student's time rather than 1 static time - time = Column(TIMESTAMP(timezone=False), default=dtime.now) + # time = Column(TIMESTAMP(timezone=False), default=dtime.now) + date = Column(DateTime(timezone=True), default=dtime.now, primary_key=True) # default=func.now + student_id = Column(Integer, ForeignKey("students.id")) # creates AttendanceModel.students as list and # backref StudentModel.attendances as AppenderQuery object # which can be accessed by StudentModel.attendances.all() - students = relationship( - "StudentModel", - secondary="student_attendances", - backref=backref("attendances", lazy="dynamic") - ) + # students = relationship( + # "StudentModel", + # # secondary="student_attendances", + # foreign_keys="StudentModel.id", + # backref=backref("attendances", lazy="dynamic") + # ) + + @classmethod + def find_by_date(cls, date: dt, student: StudentModel) -> "AttendanceModel": + return Session.query(cls).filter_by(date=date, student=student).first() @classmethod - def find_by_date(cls, date: dt) -> "AttendanceModel": - return Session.query(cls).filter_by(date=date).first() + def find_by_student(cls, student: StudentModel) -> "AttendanceModel": + return Session.query(cls).filter_by(student=student).first() @classmethod def find_by_time(cls, time: dtime) -> "AttendanceModel": @@ -109,8 +108,14 @@ def find_all(cls) -> List["AttendanceModel"]: # print(f"Date: {x.AttendanceModel.date} Name: {x.StudentModel.name} Time: {x.AttendanceModel.time}") return Session.query(cls).all() - def is_marked(self, student: StudentModel) -> bool: - return student in self.students + @classmethod + def is_marked(cls, date: dt, student: StudentModel) -> bool: + marked = AttendanceModel.find_by_date(date, student) + if marked is None: + marked = False + else: + marked = True + return marked def save_to_db(self) -> None: Session.add(self) diff --git a/backend/src/resources/video_feed.py b/backend/src/resources/video_feed.py index 015ac36..59a76a2 100644 --- a/backend/src/resources/video_feed.py +++ b/backend/src/resources/video_feed.py @@ -42,8 +42,17 @@ def get(cls, feed_id: str): video_feed = VideoFeedModel.find_by_id(feed_id) feed_url = video_feed.url camera_stream = RecognitionCamera + # Camera Device Selection if feed_url == "0": feed_url = 0 + elif feed_url == "1": + feed_url = 1 + elif feed_url == "2": + feed_url = 2 + elif feed_url == "3": + feed_url = 3 + elif feed_url == "4": + feed_url = 4 camera_stream.set_video_source(feed_url) if video_feed: resp = Response( diff --git a/backend/src/schemas.py b/backend/src/schemas.py index 7e60e8c..b484541 100644 --- a/backend/src/schemas.py +++ b/backend/src/schemas.py @@ -28,12 +28,10 @@ class AttendanceSchema(SQLAlchemyAutoSchema): class Meta: model = AttendanceModel # load_only = () # during deserialization dictionary -> object - dump_only = ("date", "time", "students") # during serialization object -> dictionary + dump_only = ("date", "student") # during serialization object -> dictionary load_instance = True # Optional: deserialize to object/model instances - # Override books field to use a nested representation rather than pks - students = Nested( - StudentSchema, - many=True + student = Nested( + StudentSchema ) diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 4b059fc..f780ef6 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -12,6 +12,7 @@ import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { TokenInterceptorService } from './services/token-interceptor.service'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { CommonModule } from '@angular/common'; @NgModule({ declarations: [ @@ -20,6 +21,7 @@ import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; ], imports: [ BrowserModule, + CommonModule, AppRoutingModule, ReactiveFormsModule, HttpClientModule, diff --git a/frontend/src/app/components/attendance/attendance.component.html b/frontend/src/app/components/attendance/attendance.component.html index d5f6367..72d9015 100644 --- a/frontend/src/app/components/attendance/attendance.component.html +++ b/frontend/src/app/components/attendance/attendance.component.html @@ -7,12 +7,10 @@ - - - {{ day.date | date:'shortDate'}} - {{ student.name }} - {{ day.time | date:'mediumTime' }} - - + + {{ attendance.date | date:'shortDate'}} + {{ attendance.student.name }} + {{ attendance.date | date:'mediumTime' }} + \ No newline at end of file