From 1ff9322df61083ab29651359c6d5674366ad321a Mon Sep 17 00:00:00 2001 From: Itay Shirizly Date: Wed, 23 Sep 2020 20:16:35 +0300 Subject: [PATCH 1/9] Added requirements.txt also some minor bug fix --- Server/__init__.py | 2 +- Server/classrooms/utils.py | 1 + requirements.txt | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 requirements.txt diff --git a/Server/__init__.py b/Server/__init__.py index b0a8c0f..264137f 100644 --- a/Server/__init__.py +++ b/Server/__init__.py @@ -10,7 +10,7 @@ db = SQLAlchemy(app) bcrypt = Bcrypt(app) login_manager = LoginManager(app) -login_manager.login_view = 'login' +login_manager.login_view = 'users.login' login_manager.login_message_category = 'info' diff --git a/Server/classrooms/utils.py b/Server/classrooms/utils.py index 6801961..e875dbe 100644 --- a/Server/classrooms/utils.py +++ b/Server/classrooms/utils.py @@ -1,5 +1,6 @@ import secrets import os +from Server import app def save_file(form_file): #TODO: create better algorithem to save the files _, f_ext = os.path.splitext(form_file.filename) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f9332bf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,19 @@ +bcrypt==3.2.0 +cffi==1.14.3 +click==7.1.2 +dnspython==2.0.0 +email-validator==1.1.1 +Flask==1.1.2 +Flask-Bcrypt==0.7.1 +Flask-Login==0.5.0 +Flask-SQLAlchemy==2.4.4 +Flask-WTF==0.14.3 +idna==2.10 +itsdangerous==1.1.0 +Jinja2==2.11.2 +MarkupSafe==1.1.1 +pycparser==2.20 +six==1.15.0 +SQLAlchemy==1.3.19 +Werkzeug==1.0.1 +WTForms==2.3.3 From b30b42e15b8add89395e11ca548c077f589ab2d3 Mon Sep 17 00:00:00 2001 From: Inbar Shirizly Date: Fri, 25 Sep 2020 00:02:46 +0300 Subject: [PATCH 2/9] #6 create function to parse xlsx, xls and csv files reads formats of mashov and format of file that the columns are in the first row --- .gitignore | 3 ++ loading_classroom_file.py | 85 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 loading_classroom_file.py diff --git a/.gitignore b/.gitignore index 50db754..3993617 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ meeting* .ipynb_checkpoints *.xlsx checking_parsing.ipynb +Server/site.db +site.db +*.xls diff --git a/loading_classroom_file.py b/loading_classroom_file.py new file mode 100644 index 0000000..dc6cc25 --- /dev/null +++ b/loading_classroom_file.py @@ -0,0 +1,85 @@ +import pandas as pd +from sqlalchemy import create_engine +import os +import numpy as np +import re + +STUDENT_FILES_FOLDER = './student_csv_examples' +EXCEL_PATH = ["דוגמה לרשימת תלמידים.xlsx", "רשימת תלמידים.xlsx", "רשימה שמית לקבוצה מתמטיקה 5 יב1 - יב4 פטרושקה רועי [185] (2).xls"] + +SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' +engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=False) + +EXCEL_COLS = { + "Name": ["שם התלמיד", "תלמידים", "שמות", "שם", "סטודנט"], + "ID": ["תעודת זהות", "ת.ז.", "ת.ז", "תז"], + "Phone": ["טלפון", "מספר טלפון", "מס טלפון"], + "Gender": ["מין"], + "org_class": ["כיתה"] + } + +MASHOW_COLS = ["Name", "org_class"] +gender_dict = {1: ["זכר", "ז", "(ז)"], 0: ["נקבה", "נ", "(נ)"]} +mashov_name_pattern = re.compile(r"([\u0590-\u05fe ]+)([(\u0590-\u05fe)]+)") + + +def gender_assign(string, gender_dict): + for key, vals in gender_dict.items(): + if string in vals: + return key + return "" + + +def parse_excel(file_path): + if file_path.endswith(".csv"): + df_students = pd.read_csv(file_path) + elif file_path.endswith(".xlsx"): + df_students = pd.read_excel(file_path) + else: + df_students = pd.read_html(file_path, header=1)[0] + + relevant_cols = [col for col in df_students.columns if not col.startswith("Unnamed")] + current_excel_dict = {} + + for col in relevant_cols: + for key, col_options in EXCEL_COLS.items(): + if col in col_options: + current_excel_dict[key] = df_students[col] + + if len(current_excel_dict) == 0 and len(relevant_cols) <= 1: + print("Mashov file") + header_index = df_students.notnull().sum(axis=1).argmax() + df_students = pd.DataFrame(df_students.values[header_index + 1:-2], columns=df_students.iloc[header_index]) + df_students.dropna(axis=0, how='all', inplace=True) + df_students.dropna(axis=1, how='all', inplace=True) + df_students.rename(columns={np.nan: 'Name', "פרטי תלמיד": 'Name', "כיתה": "org_class"}, inplace=True) + df_students = df_students.loc[:, MASHOW_COLS] + + df_name_gender = df_students.Name.str.extract(mashov_name_pattern, expand=False) + df_students['Gender'] = df_name_gender[1].str.extract("\(([\u0590-\u05fe ])\)") + df_students['Gender'] = df_students['Gender'].apply(gender_assign, gender_dict=gender_dict) + df_students['Name'] = df_name_gender[0] + + else: + df_students = pd.DataFrame(current_excel_dict) + + for col in EXCEL_COLS.keys(): + try: + df_students[col] = df_students[col] + except KeyError: + df_students[col] = pd.Series([np.nan] * df_students.shape[0]) + + return df_students[list(EXCEL_COLS.keys())] + + +if __name__ == '__main__': + for e_path in EXCEL_PATH: + student_file_path = os.path.join(STUDENT_FILES_FOLDER, e_path) + df_students = parse_excel(student_file_path) + print(df_students) + + df_students.to_sql('Students', con=engine, if_exists="append", index=False) + + student_sql = engine.execute("SELECT * FROM Students").fetchall() + for student in student_sql: + print(student) From d062fbc02e52f82aa86354b485656941bed681e9 Mon Sep 17 00:00:00 2001 From: Inbar Shirizly Date: Fri, 25 Sep 2020 00:21:34 +0300 Subject: [PATCH 3/9] update requirements.txt --- requirements.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/requirements.txt b/requirements.txt index f9332bf..ebc4e21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ bcrypt==3.2.0 +beautifulsoup4==4.9.1 +bs4==0.0.1 cffi==1.14.3 click==7.1.2 dnspython==2.0.0 @@ -8,12 +10,21 @@ Flask-Bcrypt==0.7.1 Flask-Login==0.5.0 Flask-SQLAlchemy==2.4.4 Flask-WTF==0.14.3 +html5lib==1.1 idna==2.10 itsdangerous==1.1.0 Jinja2==2.11.2 +lxml==4.5.2 MarkupSafe==1.1.1 +numpy==1.19.2 +pandas==1.1.2 pycparser==2.20 +python-dateutil==2.8.1 +pytz==2020.1 six==1.15.0 +soupsieve==2.0.1 SQLAlchemy==1.3.19 +webencodings==0.5.1 Werkzeug==1.0.1 WTForms==2.3.3 +xlrd==1.2.0 From 99c31f8c796b289e3f8457c5f558c0e891556a5d Mon Sep 17 00:00:00 2001 From: Itay Shirizly Date: Fri, 25 Sep 2020 01:07:55 +0300 Subject: [PATCH 4/9] Connected excel/csv parsing with the server #6 --- Server/classrooms/forms.py | 8 +++- .../classrooms/loading_classroom_file.py | 44 ++++++++++--------- Server/classrooms/routes.py | 24 +++++----- Server/classrooms/utils.py | 10 ----- Server/models.py | 8 ++-- Server/templates/classroom.html | 40 ++++++++++++++++- 6 files changed, 84 insertions(+), 50 deletions(-) rename loading_classroom_file.py => Server/classrooms/loading_classroom_file.py (64%) delete mode 100644 Server/classrooms/utils.py diff --git a/Server/classrooms/forms.py b/Server/classrooms/forms.py index 4e7ffca..df56f35 100644 --- a/Server/classrooms/forms.py +++ b/Server/classrooms/forms.py @@ -6,5 +6,11 @@ class CreateClassForm(FlaskForm): name = StringField('Name of the class', validators=[DataRequired()]) - students_file = FileField('Csv/Excel file of students', validators=[FileAllowed(['xlsx', 'csv']), FileRequired()]) + students_file = FileField('Csv/Excel file of students', validators=[FileAllowed(['xlsx', 'csv', 'xls']), FileRequired()]) submit = SubmitField('Create') + + +class CreateReportForm(FlaskForm): + start_sentence = StringField('Sentence to start the zoom check') + chat_file = FileField('Zoom chat file', validators=[FileRequired(), FileAllowed(['txt'])]) + submit = SubmitField('Create Report') diff --git a/loading_classroom_file.py b/Server/classrooms/loading_classroom_file.py similarity index 64% rename from loading_classroom_file.py rename to Server/classrooms/loading_classroom_file.py index dc6cc25..65ef4ef 100644 --- a/loading_classroom_file.py +++ b/Server/classrooms/loading_classroom_file.py @@ -11,14 +11,14 @@ engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=False) EXCEL_COLS = { - "Name": ["שם התלמיד", "תלמידים", "שמות", "שם", "סטודנט"], - "ID": ["תעודת זהות", "ת.ז.", "ת.ז", "תז"], - "Phone": ["טלפון", "מספר טלפון", "מס טלפון"], - "Gender": ["מין"], + "name": ["שם התלמיד", "תלמידים", "שמות", "שם", "סטודנט"], + "id_number": ["תעודת זהות", "ת.ז.", "ת.ז", "תז"], + "phone": ["טלפון", "מספר טלפון", "מס טלפון"], + "gender": ["מין"], "org_class": ["כיתה"] } -MASHOW_COLS = ["Name", "org_class"] +MASHOW_COLS = ["name", "org_class"] gender_dict = {1: ["זכר", "ז", "(ז)"], 0: ["נקבה", "נ", "(נ)"]} mashov_name_pattern = re.compile(r"([\u0590-\u05fe ]+)([(\u0590-\u05fe)]+)") @@ -30,13 +30,13 @@ def gender_assign(string, gender_dict): return "" -def parse_excel(file_path): - if file_path.endswith(".csv"): - df_students = pd.read_csv(file_path) - elif file_path.endswith(".xlsx"): - df_students = pd.read_excel(file_path) +def parse_excel(file_name, file_data): + if file_name.endswith(".csv"): + df_students = pd.read_csv(file_data) + elif file_name.endswith(".xlsx"): + df_students = pd.read_excel(file_data) else: - df_students = pd.read_html(file_path, header=1)[0] + df_students = pd.read_html(file_data, header=1)[0] relevant_cols = [col for col in df_students.columns if not col.startswith("Unnamed")] current_excel_dict = {} @@ -52,13 +52,13 @@ def parse_excel(file_path): df_students = pd.DataFrame(df_students.values[header_index + 1:-2], columns=df_students.iloc[header_index]) df_students.dropna(axis=0, how='all', inplace=True) df_students.dropna(axis=1, how='all', inplace=True) - df_students.rename(columns={np.nan: 'Name', "פרטי תלמיד": 'Name', "כיתה": "org_class"}, inplace=True) + df_students.rename(columns={np.nan: 'name', "פרטי תלמיד": 'name', "כיתה": "org_class"}, inplace=True) df_students = df_students.loc[:, MASHOW_COLS] - df_name_gender = df_students.Name.str.extract(mashov_name_pattern, expand=False) - df_students['Gender'] = df_name_gender[1].str.extract("\(([\u0590-\u05fe ])\)") - df_students['Gender'] = df_students['Gender'].apply(gender_assign, gender_dict=gender_dict) - df_students['Name'] = df_name_gender[0] + df_name_gender = df_students['name'].str.extract(mashov_name_pattern, expand=False) + df_students['gender'] = df_name_gender[1].str.extract("\(([\u0590-\u05fe ])\)") + df_students['gender'] = df_students['gender'].apply(gender_assign, gender_dict=gender_dict) + df_students['name'] = df_name_gender[0] else: df_students = pd.DataFrame(current_excel_dict) @@ -73,13 +73,15 @@ def parse_excel(file_path): if __name__ == '__main__': - for e_path in EXCEL_PATH: + for e_path in EXCEL_PATH[:1]: student_file_path = os.path.join(STUDENT_FILES_FOLDER, e_path) df_students = parse_excel(student_file_path) + + print(df_students) - df_students.to_sql('Students', con=engine, if_exists="append", index=False) + #df_students.to_sql('Students', con=engine, if_exists="append", index=False) - student_sql = engine.execute("SELECT * FROM Students").fetchall() - for student in student_sql: - print(student) + #student_sql = engine.execute("SELECT * FROM Students").fetchall() + #for student in student_sql: + # print(student) diff --git a/Server/classrooms/routes.py b/Server/classrooms/routes.py index 9eb7053..717e88f 100644 --- a/Server/classrooms/routes.py +++ b/Server/classrooms/routes.py @@ -1,9 +1,10 @@ -from flask import render_template, redirect, url_for, flash, Blueprint +from flask import render_template, redirect, url_for, flash, Blueprint, request from Server import db -from Server.classrooms.forms import CreateClassForm +from Server.classrooms.forms import CreateClassForm, CreateReportForm from Server.models import Classroom, Student from flask_login import current_user, login_required -from Server.classrooms.utils import save_file +from Server.classrooms.loading_classroom_file import parse_excel +import pandas as pd classrooms = Blueprint('classrooms', __name__) @@ -14,10 +15,15 @@ def home(): form = CreateClassForm() if form.validate_on_submit(): - file_name = save_file(form.students_file.data) + students = parse_excel(form.students_file.data.filename, form.students_file.data) new_class = Classroom(name=form.name.data, teacher=current_user) db.session.add(new_class) db.session.commit() + students['class_id'] = pd.Series([new_class.id] * students.shape[0]) + students.to_sql('student', con=db.engine, if_exists="append", index=False) + + + # return redirect(url_for('classrooms.classroom', class_id=new_class.id)) return render_template('home.html', form=form) @@ -28,11 +34,5 @@ def classroom(class_id): if current_class is None: flash('Invalid class!', 'danger') return redirect(url_for('classrooms.home')) - current_class.students = [ # Temporary hardocded data - Student(school_class='יב 1', name='איתי'), - Student(school_class='יב 2', name='ענבר', id_number=212525489), - Student(school_class='יב 3', name='לירן', id_number=3), - Student(school_class='Liran', name='hello') - ] - return render_template('classroom.html', current_class=current_class) - + form = CreateReportForm() + return render_template('classroom.html', current_class=current_class, form=form) diff --git a/Server/classrooms/utils.py b/Server/classrooms/utils.py deleted file mode 100644 index e875dbe..0000000 --- a/Server/classrooms/utils.py +++ /dev/null @@ -1,10 +0,0 @@ -import secrets -import os -from Server import app - -def save_file(form_file): #TODO: create better algorithem to save the files - _, f_ext = os.path.splitext(form_file.filename) - file_name = secrets.token_hex(8) + f_ext - file_path = os.path.join(app.root_path, 'static', 'students', file_name) - form_file.save(file_path) - return file_name diff --git a/Server/models.py b/Server/models.py index e06ad49..939a096 100644 --- a/Server/models.py +++ b/Server/models.py @@ -29,8 +29,8 @@ class Classroom(db.Model): class Student(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=False, nullable=False) - id_number = db.Column(db.String(9), unique=False) - school_class = db.Column(db.String(20), unique=False) - gender = db.Column(db.Boolean, unique=False) # True means male + id_number = db.Column(db.String(9), unique=False, nullable=True) + org_class = db.Column(db.String(20), unique=False, nullable=True) + gender = db.Column(db.Boolean, unique=False, nullable=True) # True means male class_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) - + phone = db.Column(db.Integer, unique=False, nullable=True) diff --git a/Server/templates/classroom.html b/Server/templates/classroom.html index c48a7bc..f638aae 100644 --- a/Server/templates/classroom.html +++ b/Server/templates/classroom.html @@ -16,22 +16,58 @@

Class: {{ current_class.name }}

Student Name Class Id Number + Phone + gender {% for student in current_class.students %} {{ student.name }} - {{ student.school_class }} + {{ student.org_class }} {% if student.id_number == None %} - Don't exist + {% else %} {{ student.id_number }} {% endif %} + {% if student.phone == None %} + + {% else %} + {{ student.phone }} + {% endif %} + {% if student.gender == None %} + + {% else %} + {% if student.gender %} + ז + {% else %} + נ + {% endif %} + {% endif %} {% endfor %} +
+
+
+
+
+ {{ form.chat_file.label }} + {{ form.chat_file(class="form-control-file") }} + {% if form.chat_file.errors %} + {% for error in form.chat_file.errors %} + {{ error }} + {% endfor %} + {% endif %} +
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+
+
{% endblock content %} \ No newline at end of file From 10b7becd191f715a080ede9904f3173ebbedce4b Mon Sep 17 00:00:00 2001 From: Itay Shirizly Date: Fri, 25 Sep 2020 22:51:18 +0300 Subject: [PATCH 5/9] Built basic form for later integration with the Attendance class #5 Also, created macros to remove code duplications --- .../classrooms/attendance_check.py | 0 Server/classrooms/forms.py | 7 ++- Server/classrooms/routes.py | 20 ++++-- Server/templates/_render_form_field.html | 31 +++++++++ Server/templates/classroom.html | 45 ++++++------- Server/templates/home.html | 50 +++++---------- Server/templates/login.html | 32 ++-------- Server/templates/register.html | 63 ++----------------- Server/templates/report.html | 4 ++ 9 files changed, 101 insertions(+), 151 deletions(-) rename zoom_chat_check_attendence.py => Server/classrooms/attendance_check.py (100%) create mode 100644 Server/templates/_render_form_field.html create mode 100644 Server/templates/report.html diff --git a/zoom_chat_check_attendence.py b/Server/classrooms/attendance_check.py similarity index 100% rename from zoom_chat_check_attendence.py rename to Server/classrooms/attendance_check.py diff --git a/Server/classrooms/forms.py b/Server/classrooms/forms.py index df56f35..e644b50 100644 --- a/Server/classrooms/forms.py +++ b/Server/classrooms/forms.py @@ -1,7 +1,7 @@ from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileAllowed, FileRequired from wtforms.validators import DataRequired -from wtforms import StringField, SubmitField +from wtforms import StringField, SubmitField, IntegerField class CreateClassForm(FlaskForm): @@ -11,6 +11,7 @@ class CreateClassForm(FlaskForm): class CreateReportForm(FlaskForm): - start_sentence = StringField('Sentence to start the zoom check') - chat_file = FileField('Zoom chat file', validators=[FileRequired(), FileAllowed(['txt'])]) + start_sentence = StringField('Sentence to start the zoom check', validators=[DataRequired()]) + chat_file = FileField('Zoom chat file', validators=[FileRequired(), FileAllowed(['txt'])]) + time = IntegerField('Number of minutes', validators=[DataRequired()]) submit = SubmitField('Create Report') diff --git a/Server/classrooms/routes.py b/Server/classrooms/routes.py index 717e88f..e709618 100644 --- a/Server/classrooms/routes.py +++ b/Server/classrooms/routes.py @@ -1,10 +1,11 @@ -from flask import render_template, redirect, url_for, flash, Blueprint, request +from flask import render_template, redirect, url_for, flash, Blueprint, request, session from Server import db from Server.classrooms.forms import CreateClassForm, CreateReportForm from Server.models import Classroom, Student from flask_login import current_user, login_required from Server.classrooms.loading_classroom_file import parse_excel import pandas as pd +from Server.classrooms.attendance_check import Attendance classrooms = Blueprint('classrooms', __name__) @@ -21,18 +22,25 @@ def home(): db.session.commit() students['class_id'] = pd.Series([new_class.id] * students.shape[0]) students.to_sql('student', con=db.engine, if_exists="append", index=False) - - - # return redirect(url_for('classrooms.classroom', class_id=new_class.id)) + return redirect(url_for('classrooms.classroom', class_id=new_class.id)) return render_template('home.html', form=form) -@classrooms.route('/classroom/') +@classrooms.route('/classroom/', methods=['GET', 'POST']) @login_required def classroom(class_id): current_class = Classroom.query.filter_by(id=class_id, teacher=current_user).first() # Making sure the class belongs to the current user if current_class is None: flash('Invalid class!', 'danger') return redirect(url_for('classrooms.home')) - form = CreateReportForm() + + form = CreateReportForm() + if form.validate_on_submit(): # If form was submited, creating report for the class + # TODO: Create report using Attendance class + return render_template("report.html") + + + return render_template('classroom.html', current_class=current_class, form=form) + + diff --git a/Server/templates/_render_form_field.html b/Server/templates/_render_form_field.html new file mode 100644 index 0000000..e4947f8 --- /dev/null +++ b/Server/templates/_render_form_field.html @@ -0,0 +1,31 @@ +{% macro render_field(field) %} + +
+ {{ field.label }} + {% if field.errors %} + {{ field(class="form-control is-invalid")}} +
+ {% for error in field.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ field(class="form-control")}} + {% endif %} +
+ +{% endmacro %} + +{% macro render_file_field(field) %} + +
+ {{ field.label }} + {{ field(class="form-control-file") }} + {% if field.errors %} + {% for error in field.errors %} + {{ error }} + {% endfor %} +{% endif %} +
+ +{% endmacro %} \ No newline at end of file diff --git a/Server/templates/classroom.html b/Server/templates/classroom.html index f638aae..b6fa625 100644 --- a/Server/templates/classroom.html +++ b/Server/templates/classroom.html @@ -1,13 +1,33 @@ {% extends "layout.html" %} +{% from "_render_form_field.html" import render_field, render_file_field %} + {% block content %}

Class: {{ current_class.name }}

+
+ +
+
+
+
+
+ {{ form.hidden_tag() }} + {{ render_field(form.start_sentence) }} + {{ render_file_field(form.chat_file)}} + {{ render_field(form.time) }} +
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+
+
@@ -39,9 +59,9 @@

Class: {{ current_class.name }}

{% else %} {% if student.gender %} - + {% else %} - + {% endif %} {% endif %} @@ -50,24 +70,5 @@

Class: {{ current_class.name }}

זMaleנFemale
-
-
-
-
-
- {{ form.chat_file.label }} - {{ form.chat_file(class="form-control-file") }} - {% if form.chat_file.errors %} - {% for error in form.chat_file.errors %} - {{ error }} - {% endfor %} - {% endif %} -
-
- {{ form.submit(class="btn btn-outline-info") }} -
-
-
-
-
+ {% endblock content %} \ No newline at end of file diff --git a/Server/templates/home.html b/Server/templates/home.html index 8664810..081567c 100644 --- a/Server/templates/home.html +++ b/Server/templates/home.html @@ -1,4 +1,6 @@ {% extends "layout.html" %} +{% from "_render_form_field.html" import render_field, render_file_field %} + {% block content %}
@@ -11,43 +13,21 @@

My classes

-
-
-
- {{ form.hidden_tag() }} -
-
-
- {{ form.name.label() }} - {% if form.name.errors %} - {{ form.name(class="form-control is-invalid")}} -
- {% for error in form.name.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.name(class="form-control")}} - {% endif %} -
-
-
-
- {{ form.students_file.label}} - {{ form.students_file(class="form-control-file")}} - {% if form.students_file.errors %} - {% for error in form.students_file.errors %} - {{ error }} - {% endfor %} - {% endif %} -
-
+
+ + {{ form.hidden_tag() }} +
+
+ {{ render_field(form.name) }}
-
- {{ form.submit(class="btn btn-outline-info") }} +
+ {{ render_file_field(form.students_file) }}
- -
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
diff --git a/Server/templates/login.html b/Server/templates/login.html index 8db9854..97196f5 100644 --- a/Server/templates/login.html +++ b/Server/templates/login.html @@ -1,36 +1,12 @@ {% extends "layout.html" %} +{% from "_render_form_field.html" import render_field %} + {% block content %}
{{ form.hidden_tag() }} -
- {{ form.auth.label() }} - - {% if form.auth.errors %} - {{ form.auth(class="form-control is-invalid")}} -
- {% for error in form.auth.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.auth(class="form-control")}} - {% endif %} -
-
- {{ form.password.label() }} - - {% if form.password.errors %} - {{ form.password(class="form-control is-invalid")}} -
- {% for error in form.password.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.password(class="form-control")}} - {% endif %} -
+ {{ render_field(form.auth) }} + {{ render_field(form.password)}}
{{ form.remember(class="form-check-input") }} {{ form.remember.label() }} diff --git a/Server/templates/register.html b/Server/templates/register.html index c24a05d..0f4cb2b 100644 --- a/Server/templates/register.html +++ b/Server/templates/register.html @@ -1,64 +1,13 @@ {% extends "layout.html" %} +{% from "_render_form_field.html" import render_field %} + {% block content %} {{ form.hidden_tag() }} -
- {{ form.username.label() }} - - {% if form.username.errors %} - {{ form.username(class="form-control is-invalid")}} -
- {% for error in form.username.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.username(class="form-control")}} - {% endif %} -
-
- {{ form.email.label() }} - - {% if form.email.errors %} - {{ form.email(class="form-control is-invalid")}} -
- {% for error in form.email.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.email(class="form-control")}} - {% endif %} - -
-
- {{ form.password.label() }} - - {% if form.password.errors %} - {{ form.password(class="form-control is-invalid")}} -
- {% for error in form.password.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.password(class="form-control")}} - {% endif %} -
-
- {{ form.confirm_password.label() }} - - {% if form.confirm_password.errors %} - {{ form.confirm_password(class="form-control is-invalid")}} -
- {% for error in form.confirm_password.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.confirm_password(class="form-control")}} - {% endif %} -
+ {{ render_field(form.username) }} + {{ render_field(form.email) }} + {{ render_field(form.password) }} + {{ render_field(form.confirm_password) }}
{{ form.submit(class="btn btn-outline-info")}}
diff --git a/Server/templates/report.html b/Server/templates/report.html new file mode 100644 index 0000000..0f25d3b --- /dev/null +++ b/Server/templates/report.html @@ -0,0 +1,4 @@ +{% extends "layout.html" %} +{% block content %} +

Reports file

+{% endblock content %} \ No newline at end of file From dc1b84b9e5c7d5f175f96940a19506b2e09702ca Mon Sep 17 00:00:00 2001 From: Inbar Shirizly Date: Sat, 26 Sep 2020 16:42:50 +0300 Subject: [PATCH 6/9] #6 #5 changed parsing and classrom functions to classes with init and config. integrate parsing zoom chat file with input from server --- Server/classrooms/__init__.py | 5 + Server/classrooms/attendance_check.py | 71 ++-------- Server/classrooms/loading_classroom_file.py | 141 ++++++++------------ Server/classrooms/routes.py | 15 ++- Server/classrooms/utils.py | 27 ++++ Server/config.py | 15 ++- conf.py | 13 -- 7 files changed, 129 insertions(+), 158 deletions(-) create mode 100644 Server/classrooms/utils.py delete mode 100644 conf.py diff --git a/Server/classrooms/__init__.py b/Server/classrooms/__init__.py index e69de29..daaea2c 100644 --- a/Server/classrooms/__init__.py +++ b/Server/classrooms/__init__.py @@ -0,0 +1,5 @@ +from Server.config import ParseConfig +from Server.classrooms.loading_classroom_file import ParseClassFile + + +parser = ParseClassFile.from_object(ParseConfig) \ No newline at end of file diff --git a/Server/classrooms/attendance_check.py b/Server/classrooms/attendance_check.py index 182ca72..e41ca8d 100644 --- a/Server/classrooms/attendance_check.py +++ b/Server/classrooms/attendance_check.py @@ -1,59 +1,29 @@ -""" ------------ zoom check attendance ----------- -Program implementations: -a. gets a text file - zoom chat from class -b. find session when an attendance check has been initiated and look for all the participators who wrote - something meaningful in the chat before or after the check. function checks allows user to write a name of the - student list only once. in case user had a mistake, it will check all his messages until it find the relevant one -c. return df contains all the users from the csv/excel file with new columns that have: zoom name/ Nan - where zoom name - means that this zoom user added the relevant name and Nan means missing student -d. retrun df from each session with the messages of zoom users that did not write something related to the actuall name - (allows the teacher to check if they had type error and then add them manually) - -""" - -from datetime import datetime import numpy as np import pandas as pd -import re -import conf - -# configuration variables -FILE_NAME = ".\chat files\meeting_example_full_name.txt" -EXCEL_PATH = "דוגמה לרשימת תלמידים.xlsx" -SENTENCE_START = "attendance check" -TIME_DELTA = 1 # time period in minuets -WORDS_FOR_NOT_INCLUDED_PARTICIPATORS = ["ITC", "Tech", "Challenge"] class Attendance: - - def __init__(self, chat_path, student_file_path, filter_modes, time_delta, start_sentence): + """ + receives student class df, the zoom chat and other configuration. returns: + 1. table of attendant students from the student class df + 2. list of table of relevant data from zoom users that didn't add a student return + """ + def __init__(self, chat_df, students_df, filter_modes, time_delta, start_sentence): """ - convert the chat text file to a data frame and arrange columns. - creates df for each session according the appearance of the start sentence and time delta - - create df from the excel or csv file of the studnets - :param chat_path: the path of the relevant chat file (str) - :param student_file_path: path for the relevant csv/excel file (str) + :param chat_df: zoom chat in df (df) + :param students_df: student class raw data (df) :param filter_modes: filters the user picked for parsing the text file (list of str) :param time_delta: max time from start sentence to the last message to parse in each session in minutes (int) :param start_sentence: start sentence that initiate sessions for parse (str) :return: data frame with the data from the chat """ - with open(chat_path, "r", encoding="utf-8") as file: - data = [re.search(conf.CHAT_PATTERN, line).groups() for line in file if re.match(conf.CHAT_PATTERN, line)] - df = pd.DataFrame(data, columns=["time", "users", "chat"]) - df['chat'] = df['chat'].str[:-1] - df["time"] = df["time"].apply(lambda string: datetime.strptime(string, "%H:%M:%S")) - - start_indices = df.index[df['chat'].apply(lambda string: start_sentence.lower() in string.lower())] - self.df_sessions = [self.get_df_of_time_segment(df, start_index, time_delta) for start_index in start_indices] + start_indices = chat_df.index[chat_df['chat'].apply(lambda string: start_sentence.lower() in string.lower())] + self.df_sessions = [self.get_df_of_time_segment(chat_df, start_index, time_delta) for start_index in start_indices] self.filter_modes = filter_modes - if student_file_path.endswith(".csv"): - self.df_students = pd.read_csv(student_file_path, usecols=conf.EXCEL_COLS.values()).astype("str") - else: - self.df_students = pd.read_excel(student_file_path, usecols=conf.EXCEL_COLS.values()).astype("str") + self.df_students = students_df.astype(str) @staticmethod def get_df_of_time_segment(df, start_index, time_delta): @@ -76,15 +46,15 @@ def get_participants(self, df_chat): """ final_df = None for mode in self.filter_modes: - merged_df = pd.merge(self.df_students, df_chat, left_on=conf.EXCEL_COLS[mode], right_on="chat", how="left") + merged_df = pd.merge(self.df_students, df_chat, left_on=mode, right_on="chat", how="left") final_df = pd.concat([merged_df, final_df]) final_df.sort_values(by="time", inplace=True) df_participated = final_df.drop(columns=["chat", "time"]) df_participated = df_participated.groupby("users").first().reset_index() - df_participated_updated = pd.merge(self.df_students, df_participated, left_on=conf.EXCEL_COLS["Name"], - right_on=conf.EXCEL_COLS["Name"], how="left", suffixes=["_x", ""]) + df_participated_updated = pd.merge(self.df_students, df_participated, left_on="name", + right_on="name", how="left", suffixes=["_x", ""]) overlapping_columns = df_participated_updated.columns[df_participated_updated.columns.str.contains("_x")] df_participated_updated = df_participated_updated.drop(columns=overlapping_columns) return df_participated_updated @@ -128,17 +98,4 @@ def get_attendance(self, not_included_part): return attendance_df, df_zoom_not_correct_list -def main(): - my_class = Attendance(FILE_NAME, EXCEL_PATH, ['Name', "ID", "Phone"], TIME_DELTA, SENTENCE_START) - attendance_df, df_zoom_not_correct_list = my_class.get_attendance(WORDS_FOR_NOT_INCLUDED_PARTICIPATORS) - - print(attendance_df) - - for i in range(len(df_zoom_not_correct_list)): - print(f"zoom session {i + 1}") - print(df_zoom_not_correct_list[i]) - - -if __name__ == '__main__': - main() diff --git a/Server/classrooms/loading_classroom_file.py b/Server/classrooms/loading_classroom_file.py index 65ef4ef..a02e126 100644 --- a/Server/classrooms/loading_classroom_file.py +++ b/Server/classrooms/loading_classroom_file.py @@ -1,87 +1,64 @@ import pandas as pd -from sqlalchemy import create_engine -import os import numpy as np import re -STUDENT_FILES_FOLDER = './student_csv_examples' -EXCEL_PATH = ["דוגמה לרשימת תלמידים.xlsx", "רשימת תלמידים.xlsx", "רשימה שמית לקבוצה מתמטיקה 5 יב1 - יב4 פטרושקה רועי [185] (2).xls"] -SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' -engine = create_engine(SQLALCHEMY_DATABASE_URI, echo=False) - -EXCEL_COLS = { - "name": ["שם התלמיד", "תלמידים", "שמות", "שם", "סטודנט"], - "id_number": ["תעודת זהות", "ת.ז.", "ת.ז", "תז"], - "phone": ["טלפון", "מספר טלפון", "מס טלפון"], - "gender": ["מין"], - "org_class": ["כיתה"] - } - -MASHOW_COLS = ["name", "org_class"] -gender_dict = {1: ["זכר", "ז", "(ז)"], 0: ["נקבה", "נ", "(נ)"]} -mashov_name_pattern = re.compile(r"([\u0590-\u05fe ]+)([(\u0590-\u05fe)]+)") - - -def gender_assign(string, gender_dict): - for key, vals in gender_dict.items(): - if string in vals: - return key - return "" - - -def parse_excel(file_name, file_data): - if file_name.endswith(".csv"): - df_students = pd.read_csv(file_data) - elif file_name.endswith(".xlsx"): - df_students = pd.read_excel(file_data) - else: - df_students = pd.read_html(file_data, header=1)[0] - - relevant_cols = [col for col in df_students.columns if not col.startswith("Unnamed")] - current_excel_dict = {} - - for col in relevant_cols: - for key, col_options in EXCEL_COLS.items(): - if col in col_options: - current_excel_dict[key] = df_students[col] - - if len(current_excel_dict) == 0 and len(relevant_cols) <= 1: - print("Mashov file") - header_index = df_students.notnull().sum(axis=1).argmax() - df_students = pd.DataFrame(df_students.values[header_index + 1:-2], columns=df_students.iloc[header_index]) - df_students.dropna(axis=0, how='all', inplace=True) - df_students.dropna(axis=1, how='all', inplace=True) - df_students.rename(columns={np.nan: 'name', "פרטי תלמיד": 'name', "כיתה": "org_class"}, inplace=True) - df_students = df_students.loc[:, MASHOW_COLS] - - df_name_gender = df_students['name'].str.extract(mashov_name_pattern, expand=False) - df_students['gender'] = df_name_gender[1].str.extract("\(([\u0590-\u05fe ])\)") - df_students['gender'] = df_students['gender'].apply(gender_assign, gender_dict=gender_dict) - df_students['name'] = df_name_gender[0] - - else: - df_students = pd.DataFrame(current_excel_dict) - - for col in EXCEL_COLS.keys(): - try: - df_students[col] = df_students[col] - except KeyError: - df_students[col] = pd.Series([np.nan] * df_students.shape[0]) - - return df_students[list(EXCEL_COLS.keys())] - - -if __name__ == '__main__': - for e_path in EXCEL_PATH[:1]: - student_file_path = os.path.join(STUDENT_FILES_FOLDER, e_path) - df_students = parse_excel(student_file_path) - - - print(df_students) - - #df_students.to_sql('Students', con=engine, if_exists="append", index=False) - - #student_sql = engine.execute("SELECT * FROM Students").fetchall() - #for student in student_sql: - # print(student) +class ParseClassFile: + + def __init__(self, file_cols_dict, mashov_cols, gender_dict): + self._file_cols_dict = file_cols_dict + self._mashov_cols = mashov_cols + self._gender_dict = gender_dict + + @classmethod + def from_object(cls, config): + return cls( + config.FILE_COLS_DICT, + config.MASHOV_COLS, + config.GENDER_DICT + ) + + + def parse_df(self, df_students): + relevant_cols = [col for col in df_students.columns if not col.startswith("Unnamed")] + current_excel_dict = {} + + for col in relevant_cols: + for key, col_options in self._file_cols_dict.items(): + if col in col_options: + current_excel_dict[key] = df_students[col] + + if len(current_excel_dict) == 0 and len(relevant_cols) <= 1: + print("Mashov file") + header_index = df_students.notnull().sum(axis=1).argmax() + df_students = pd.DataFrame(df_students.values[header_index + 1:-2], columns=df_students.iloc[header_index]) + df_students.dropna(axis=0, how='all', inplace=True) + df_students.dropna(axis=1, how='all', inplace=True) + df_students.rename(columns={np.nan: 'name', "פרטי תלמיד": 'name', "כיתה": "org_class"}, inplace=True) + df_students = df_students.loc[:, self._mashov_cols] + + mashov_name_pattern = re.compile(r"([\u0590-\u05fe ]+)([(\u0590-\u05fe)]+)") + df_name_gender = df_students['name'].str.extract(mashov_name_pattern, expand=False) + df_students['gender'] = df_name_gender[1].str.extract("\(([\u0590-\u05fe ])\)") + df_students['gender'] = df_students['gender'].apply(self.gender_assign, gender_dict=self._gender_dict) + df_students['name'] = df_name_gender[0] + + else: + df_students = pd.DataFrame(current_excel_dict) + + for col in self._file_cols_dict.keys(): + try: + df_students[col] = df_students[col] + except KeyError: + df_students[col] = pd.Series([np.nan] * df_students.shape[0]) + + final_df = df_students[list(self._file_cols_dict.keys())] + + return final_df.reset_index().drop(columns="index") + + @staticmethod + def gender_assign(string, gender_dict): + for key, vals in gender_dict.items(): + if string in vals: + return key + return "" \ No newline at end of file diff --git a/Server/classrooms/routes.py b/Server/classrooms/routes.py index e709618..0acc7f7 100644 --- a/Server/classrooms/routes.py +++ b/Server/classrooms/routes.py @@ -3,9 +3,10 @@ from Server.classrooms.forms import CreateClassForm, CreateReportForm from Server.models import Classroom, Student from flask_login import current_user, login_required -from Server.classrooms.loading_classroom_file import parse_excel import pandas as pd from Server.classrooms.attendance_check import Attendance +from Server.classrooms.utils import create_chat_df, create_students_df +from Server.classrooms import parser classrooms = Blueprint('classrooms', __name__) @@ -16,7 +17,8 @@ def home(): form = CreateClassForm() if form.validate_on_submit(): - students = parse_excel(form.students_file.data.filename, form.students_file.data) + students_df = create_students_df(form.students_file.data.filename, form.students_file.data) + students = parser.parse_df(students_df) new_class = Classroom(name=form.name.data, teacher=current_user) db.session.add(new_class) db.session.commit() @@ -35,12 +37,15 @@ def classroom(class_id): return redirect(url_for('classrooms.home')) form = CreateReportForm() - if form.validate_on_submit(): # If form was submited, creating report for the class - # TODO: Create report using Attendance class + if form.validate_on_submit(): # If form was submitted, creating report for the class + students_df = pd.read_sql(Student.query.filter_by(class_id=class_id).statement, con=db.engine) + chat_df = create_chat_df(form.chat_file.data.stream) + + my_class = Attendance(chat_df, students_df, ['name', "id_number", "phone"], form.time.data, form.start_sentence.data) + attendance_df, df_zoom_not_correct_list = my_class.get_attendance(["ITC", "Tech", "Challenge"]) return render_template("report.html") - return render_template('classroom.html', current_class=current_class, form=form) diff --git a/Server/classrooms/utils.py b/Server/classrooms/utils.py new file mode 100644 index 0000000..4ee5fe9 --- /dev/null +++ b/Server/classrooms/utils.py @@ -0,0 +1,27 @@ +import re +from datetime import datetime +import pandas as pd + +def create_chat_df(chat_stream): + + chat_file = chat_stream.read().decode("utf-8").split("\n") + regex_pattern = re.compile(r"(^\d{2}.\d{2}.\d{2})\s+From\s\s([\s\S]+)\s:\s([\s\S]+)") + chat_content = [re.search(regex_pattern, line).groups() for line in chat_file if re.match(regex_pattern, line)] + + chat_df = pd.DataFrame(chat_content, columns=["time", "users", "chat"]) + chat_df['chat'] = chat_df['chat'].str[:-1].astype(str) + chat_df["time"] = chat_df["time"].apply(lambda string: datetime.strptime(string, "%H:%M:%S")) + + return chat_df + + +def create_students_df(file_name, file_data): + + if file_name.endswith(".csv"): + df_students = pd.read_csv(file_data) + elif file_name.endswith(".xlsx"): + df_students = pd.read_excel(file_data) + else: + df_students = pd.read_html(file_data, header=1)[0] + + return df_students \ No newline at end of file diff --git a/Server/config.py b/Server/config.py index 30a47cf..f2768b6 100644 --- a/Server/config.py +++ b/Server/config.py @@ -1,3 +1,16 @@ class FlaskConfig: SECRET_KEY = 'TEMP_SECRET_KEY' - SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' \ No newline at end of file + SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db' + + +class ParseConfig: + + FILE_COLS_DICT = { + "name": ["שם התלמיד", "תלמידים", "שמות", "שם", "סטודנט"], + "id_number": ["תעודת זהות", "ת.ז.", "ת.ז", "תז"], + "phone": ["טלפון", "מספר טלפון", "מס טלפון"], + "gender": ["מין"], + "org_class": ["כיתה"] + } + MASHOV_COLS = ["name", "org_class"] + GENDER_DICT = {1: ["זכר", "ז", "(ז)"], 0: ["נקבה", "נ", "(נ)"]} \ No newline at end of file diff --git a/conf.py b/conf.py deleted file mode 100644 index be734a5..0000000 --- a/conf.py +++ /dev/null @@ -1,13 +0,0 @@ -import re -import numpy as np - - -EXCEL_COLS = {"Name": "שם התלמיד", - "ID": "תעודת זהות", - "Phone": "טלפון"} - - -# constants -CHAT_PATTERN = re.compile(r"(^\d{2}.\d{2}.\d{2})\s+From\s\s([\s\S]+)\s:\s([\s\S]+)") - - From 6738491756e4ac5762661650629010be74dc6fea Mon Sep 17 00:00:00 2001 From: Inbar Shirizly Date: Sat, 26 Sep 2020 22:50:45 +0300 Subject: [PATCH 7/9] #8 constructed the general database - integrates only with the excel file add creation of site.db if not exist --- Server/classrooms/attendance_check.py | 3 ++ Server/classrooms/routes.py | 3 ++ Server/models.py | 67 +++++++++++++++++++++++---- Server/users/forms.py | 6 +-- Server/users/routes.py | 8 ++-- run.py | 6 ++- 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/Server/classrooms/attendance_check.py b/Server/classrooms/attendance_check.py index e41ca8d..d30eb9e 100644 --- a/Server/classrooms/attendance_check.py +++ b/Server/classrooms/attendance_check.py @@ -1,6 +1,9 @@ import numpy as np import pandas as pd +#TODO: need to return time of first message, time of first message in each session +# TODO: return df unique to each session for the users that were present +# TODO: df_zoom_not_correct_list - change to df of all chat messages that were not relevant and the messages that were included class Attendance: """ diff --git a/Server/classrooms/routes.py b/Server/classrooms/routes.py index 0acc7f7..d8ac11f 100644 --- a/Server/classrooms/routes.py +++ b/Server/classrooms/routes.py @@ -7,6 +7,7 @@ from Server.classrooms.attendance_check import Attendance from Server.classrooms.utils import create_chat_df, create_students_df from Server.classrooms import parser +from datetime import datetime classrooms = Blueprint('classrooms', __name__) @@ -38,6 +39,8 @@ def classroom(class_id): form = CreateReportForm() if form.validate_on_submit(): # If form was submitted, creating report for the class + description = "this is the best class" # TODO : add from the form file + report_date = datetime.now().date() # TODO : add from the form file students_df = pd.read_sql(Student.query.filter_by(class_id=class_id).statement, con=db.engine) chat_df = create_chat_df(form.chat_file.data.stream) diff --git a/Server/models.py b/Server/models.py index 939a096..a04db48 100644 --- a/Server/models.py +++ b/Server/models.py @@ -4,33 +4,82 @@ @login_manager.user_loader def load_user(user_id): - return User.query.get(int(user_id)) + return Teacher.query.get(int(user_id)) -class User(db.Model, UserMixin): +class Teacher(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(40), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password = db.Column(db.String(60), nullable=False) # 60 chars because of the hashing algo - classes = db.relationship('Classroom', backref='teacher', lazy=True) + classrooms = db.relationship('Classroom', backref='teacher', lazy=True) def __repr__(self): - return f'User({self.username}, {self.email}, {self.password})' + return f'Teacher({self.username}, {self.email}, {self.password})' class Classroom(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=False, nullable=False) - teacher_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - # TODO: also store the reports in the db + teacher_id = db.Column(db.Integer, db.ForeignKey('teacher.id'), nullable=False) students = db.relationship('Student', backref='classroom', lazy=True) + reports = db.relationship('Report', backref='classroom', lazy=True) class Student(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=False, nullable=False) - id_number = db.Column(db.String(9), unique=False, nullable=True) + id_number = db.Column(db.String(10), unique=False, nullable=True) org_class = db.Column(db.String(20), unique=False, nullable=True) - gender = db.Column(db.Boolean, unique=False, nullable=True) # True means male - class_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) + gender = db.Column(db.Boolean, unique=False, nullable=True) # True means male phone = db.Column(db.Integer, unique=False, nullable=True) + + class_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) + student_in_session = db.relationship('StudentInSession', backref='student', lazy=True) + + +class Report(db.Model): + id = db.Column(db.Integer, primary_key=True) + description = db.Column(db.Text(), unique=False, nullable=True) + start_time = db.Column(db.DateTime(), unique=False, nullable=False) # first timestamp of chat file + report_date = db.Column(db.Date(), unique=False, nullable=True) # date of the report - given by the user + + class_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) + sessions = db.relationship('Session', backref='report', lazy=True) + + +class Session(db.Model): + id = db.Column(db.Integer, primary_key=True) + start_time = db.Column(db.DateTime(), unique=False, nullable=False) # first timestamp of chat session + + report_id = db.Column(db.Integer, db.ForeignKey('report.id'), nullable=False) + student_in_session = db.relationship('StudentInSession', backref='session', lazy=True) + + +class ZoomNames(db.Model): + __tablename__ = 'zoom_names' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String(50), unique=False, nullable=True) + + chat = db.relationship('Chat', backref='zoom_names', lazy=True) + student_in_session = db.relationship('StudentInSession', backref='zoom_names', lazy=True) + + +class Chat(db.Model): + id = db.Column(db.Integer, primary_key=True) + time = db.Column(db.DateTime, unique=False, nullable=False) # time the message written + message = db.Column(db.Text, unique=False, nullable=True) # message zoom user wrote + relevant = db.Column(db.Boolean, unique=False, nullable=False) # True is message is part of the report + zoom_names_id = db.Column(db.Integer, db.ForeignKey('zoom_names.id'), nullable=False) + + +class StudentInSession(db.Model): + __tablename__ = 'student_in_session' + id = db.Column(db.Integer, primary_key=True) + + student_id = db.Column(db.Integer, db.ForeignKey('student.id'), nullable=False) + session_id = db.Column(db.Integer, db.ForeignKey('session.id'), nullable=False) + zoom_names_id = db.Column(db.Integer, db.ForeignKey('zoom_names.id'), nullable=False) + + + diff --git a/Server/users/forms.py b/Server/users/forms.py index 9d157c7..68aaeb7 100644 --- a/Server/users/forms.py +++ b/Server/users/forms.py @@ -1,7 +1,7 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField, BooleanField from wtforms.validators import DataRequired, EqualTo, Length, Email, ValidationError -from Server.models import User +from Server.models import Teacher class RegistrationForm(FlaskForm): @@ -12,14 +12,14 @@ class RegistrationForm(FlaskForm): submit = SubmitField('Sign Up') def validate_username(self, username): - user = User.query.filter_by(username=username.data).first() + user = Teacher.query.filter_by(username=username.data).first() if user: print(user) raise ValidationError('Username already taken.') # Add special signs validations def validate_email(self, email): - user = User.query.filter_by(email=email.data).first() + user = Teacher.query.filter_by(email=email.data).first() if user: raise ValidationError('Email already taken.') diff --git a/Server/users/routes.py b/Server/users/routes.py index 5c95cd0..f320f70 100644 --- a/Server/users/routes.py +++ b/Server/users/routes.py @@ -1,7 +1,7 @@ from flask import render_template, redirect, url_for, flash, Blueprint from Server import bcrypt, db from Server.users.forms import LoginForm, RegistrationForm -from Server.models import User +from Server.models import Teacher from flask_login import login_user, current_user, logout_user, login_required users = Blueprint('users', __name__) @@ -13,8 +13,8 @@ def login(): form = LoginForm() if form.validate_on_submit(): - user = User.query.filter_by(email=form.auth.data).first() or \ - User.query.filter_by(username=form.auth.data).first() # User can be validated with both username and email + user = Teacher.query.filter_by(email=form.auth.data).first() or \ + Teacher.query.filter_by(username=form.auth.data).first() # User can be validated with both username and email if user and bcrypt.check_password_hash(user.password, form.password.data): login_user(user, remember=form.remember.data) return redirect(url_for('classrooms.home')) @@ -32,7 +32,7 @@ def register(): form = RegistrationForm() if form.validate_on_submit(): hashed_password = bcrypt.generate_password_hash((form.password.data)).decode('utf-8') - user = User( + user = Teacher( username=form.username.data, password=hashed_password, email=form.email.data diff --git a/run.py b/run.py index 5777dc4..6c193e7 100644 --- a/run.py +++ b/run.py @@ -1,4 +1,8 @@ -from Server import app +from Server import app, db +import os if __name__ == '__main__': + if not os.path.exists("/Server/site.db"): + db.create_all() + db.session.commit() app.run(debug=True) \ No newline at end of file From 3cfcf763a3a6c910d4b012728edc3a1f563e7078 Mon Sep 17 00:00:00 2001 From: Inbar Shirizly Date: Fri, 2 Oct 2020 17:46:43 +0300 Subject: [PATCH 8/9] #8 build logic and inserting data of report to the database fill for each session, and all chat of each zoom user --- Server/classrooms/attendance_check.py | 112 +++++++++++++------------- Server/classrooms/routes.py | 36 +++++++-- Server/classrooms/utils.py | 7 +- Server/models.py | 53 +++++------- Server/users/forms.py | 6 +- Server/users/routes.py | 8 +- 6 files changed, 118 insertions(+), 104 deletions(-) diff --git a/Server/classrooms/attendance_check.py b/Server/classrooms/attendance_check.py index d30eb9e..92b78a6 100644 --- a/Server/classrooms/attendance_check.py +++ b/Server/classrooms/attendance_check.py @@ -1,9 +1,6 @@ import numpy as np import pandas as pd -#TODO: need to return time of first message, time of first message in each session -# TODO: return df unique to each session for the users that were present -# TODO: df_zoom_not_correct_list - change to df of all chat messages that were not relevant and the messages that were included class Attendance: """ @@ -11,7 +8,7 @@ class Attendance: 1. table of attendant students from the student class df 2. list of table of relevant data from zoom users that didn't add a student return """ - def __init__(self, chat_df, students_df, filter_modes, time_delta, start_sentence): + def __init__(self, chat_df, students_df, filter_modes, time_delta, start_sentence, not_included_zoom_users): """ - convert the chat text file to a data frame and arrange columns. - creates df for each session according the appearance of the start sentence and time delta @@ -23,10 +20,14 @@ def __init__(self, chat_df, students_df, filter_modes, time_delta, start_sentenc :return: data frame with the data from the chat """ - start_indices = chat_df.index[chat_df['chat'].apply(lambda string: start_sentence.lower() in string.lower())] - self.df_sessions = [self.get_df_of_time_segment(chat_df, start_index, time_delta) for start_index in start_indices] - self.filter_modes = filter_modes - self.df_students = students_df.astype(str) + self.first_message_time = chat_df["time"].sort_values().iloc[0] + start_indices = chat_df.index[chat_df['message'].apply(lambda string: start_sentence.lower() in string.lower())] #TODO: slice by time or by next message + df_students_for_report = students_df.set_index("id").astype(str).reset_index() # set all columns to str except the id + + self._sessions = [] + for start_index in start_indices: + df_session = Attendance.get_df_of_time_segment(chat_df, start_index, time_delta) + self._sessions.append(Session(df_students_for_report, df_session, filter_modes, not_included_zoom_users)) @staticmethod def get_df_of_time_segment(df, start_index, time_delta): @@ -38,8 +39,20 @@ def get_df_of_time_segment(df, start_index, time_delta): return df.loc[time_filt] + @property + def report_sessions(self): + return self._sessions + +class Session: + + def __init__(self, df_students, df_session_chat, filter_modes, not_included_zoom_users): + + self._first_message_time = df_session_chat["time"].sort_values().iloc[0] + self._relevant_chat = self.get_participants_in_session(df_students,filter_modes, df_session_chat, not_included_zoom_users) + - def get_participants(self, df_chat): + @ staticmethod + def get_participants_in_session(df_students, filter_modes, df_chat, not_included_zoom_users): """ finds students that attendant to the session. runs over each mode which represent different way to declare that the student attendant (for example: phone number, ID). merges this data to the csv table with the zoom name that @@ -48,57 +61,48 @@ def get_participants(self, df_chat): :return: df of the attendance in the session """ final_df = None - for mode in self.filter_modes: - merged_df = pd.merge(self.df_students, df_chat, left_on=mode, right_on="chat", how="left") + for mode in filter_modes: + merged_df = pd.merge(df_students, df_chat.reset_index(), left_on=mode, right_on="message", how="left") final_df = pd.concat([merged_df, final_df]) final_df.sort_values(by="time", inplace=True) - df_participated = final_df.drop(columns=["chat", "time"]) - df_participated = df_participated.groupby("users").first().reset_index() + df_participated = final_df.groupby("zoom_name").first().reset_index() + df_participated["index"] = df_participated["index"].astype(int) + df_participated = df_participated.loc[:, ["id", "zoom_name", "time", "message", "index"]].set_index("index") - df_participated_updated = pd.merge(self.df_students, df_participated, left_on="name", - right_on="name", how="left", suffixes=["_x", ""]) - overlapping_columns = df_participated_updated.columns[df_participated_updated.columns.str.contains("_x")] - df_participated_updated = df_participated_updated.drop(columns=overlapping_columns) - return df_participated_updated + filt = df_chat['zoom_name'].str.contains('|'.join(not_included_zoom_users)) + df_relevant_chat = pd.merge(df_chat[~filt], df_participated, how="left") - @staticmethod - def get_zoom_users_not_included(df_participated, df_chat, not_included_part): - """ - get the zoom users who wrote something in the chat but it was not related to the csv/excel users info - - drops not relevant records from the df of zoom users that are not part of the class - - drops the zoom users who wrote something meaningful - :param df_participated: output of participators - that contains Nan for csv student that weren't mentioned (df) - :param df_chat: full chat of the relevant session (df) - :param not_included_part: zoom user name that will not be included (list of str) - :return: table of zoom users and there messages in the session (df) - """ - filt = ~(df_chat['users'].isin(df_participated['users'].dropna())) & \ - ~(df_chat['users'].str.contains('|'.join(not_included_part))) - df_zoom_not_correct = df_chat[filt] - return df_zoom_not_correct.drop(columns=["time"]).set_index("users") + df_relevant_chat["relevant"] = df_relevant_chat["id"].apply(lambda x: 1 if x == x else 0) + df_relevant_chat["id"] = df_relevant_chat["id"].apply(lambda x: int(x) if x == x else -1) + return df_relevant_chat - def get_attendance(self, not_included_part): - """ - - get table of attendant student from the csv/excel file and list of table of relevant data from zoom users that - didn't add a student. - - runs over all the session, for each one take the zoom user names and add it as a column to the final df - - if there is Nan it means that the student was missing in that session. the zoom user name is the user who added - the specific student. - - for each session append to a list all the df represent table of relevant data from zoom users that - didn't add a student - :param not_included_part: zoom user name that will not be included (list of str) - :return: 1. table of attendant student from the csv/excel file, - 2. list of table of relevant data from zoom users that didn't add a student - """ - df_zoom_not_correct_list = [] - attendance_df = self.df_students - for i, session in enumerate(self.df_sessions): - df_participated = self.get_participants(session) - attendance_df[f'session {i + 1}'] = df_participated['users'] - df_zoom_not_correct = self.get_zoom_users_not_included(df_participated, session, not_included_part) - df_zoom_not_correct_list.append(df_zoom_not_correct) - return attendance_df, df_zoom_not_correct_list + def zoom_names_table(self, session_id): + zoom_df = self._relevant_chat.loc[:, ["zoom_name", "id"]].rename(columns={"zoom_name": "name", "id": "student_id"}) + zoom_df['session_id'] = pd.Series([session_id] * zoom_df.shape[0]) + return zoom_df.sort_values(by="student_id", ascending=False).groupby("name").first().reset_index() + + def chat_table(self, zoom_df): + relevant_chat = self._relevant_chat.drop(columns=["id"]) + chat_session_table = pd.merge(relevant_chat, zoom_df, left_on="zoom_name", right_on="name") + return chat_session_table.drop(columns=["zoom_name", "name", "session_id", "student_id"]).rename(columns={"id": "zoom_names_id"}) + + + +if __name__ == '__main__': + from utils import create_chat_df, create_students_df + + chat_file_path = r"C:\Users\Inbar Shirizly\Documents\python\useful\ITC_programs\zoom_attendance_check\chat files\meeting_example_full_name.txt" + excel_file_path = r"C:\Users\Inbar Shirizly\Documents\python\useful\ITC_programs\zoom_attendance_check\student_csv_examples\example_data_already_prepared.xlsx" + + + with open(chat_file_path, "r", encoding="utf-8") as f: + chat_df = create_chat_df(f.readlines()) + df_students = create_students_df(file_name=excel_file_path.split("\\")[-1], file_data=excel_file_path) + + my_class = Attendance(chat_df, df_students, ['name', "id_number", "phone"], 5, "Attendance check", ["ITC", "Tech", "Challenge"]) + df_part_session = my_class._sessions[0] + df_part_session.zoom_names_table(2) diff --git a/Server/classrooms/routes.py b/Server/classrooms/routes.py index d8ac11f..4112dfa 100644 --- a/Server/classrooms/routes.py +++ b/Server/classrooms/routes.py @@ -1,7 +1,7 @@ from flask import render_template, redirect, url_for, flash, Blueprint, request, session from Server import db from Server.classrooms.forms import CreateClassForm, CreateReportForm -from Server.models import Classroom, Student +from Server.models import ClassroomModel, StudentModel, ReportModel, SessionModel, ZoomNamesModel, ChatModel from flask_login import current_user, login_required import pandas as pd from Server.classrooms.attendance_check import Attendance @@ -20,11 +20,11 @@ def home(): if form.validate_on_submit(): students_df = create_students_df(form.students_file.data.filename, form.students_file.data) students = parser.parse_df(students_df) - new_class = Classroom(name=form.name.data, teacher=current_user) + new_class = ClassroomModel(name=form.name.data, teacher_model=current_user) db.session.add(new_class) db.session.commit() students['class_id'] = pd.Series([new_class.id] * students.shape[0]) - students.to_sql('student', con=db.engine, if_exists="append", index=False) + students.to_sql('student_model', con=db.engine, if_exists="append", index=False) return redirect(url_for('classrooms.classroom', class_id=new_class.id)) return render_template('home.html', form=form) @@ -32,7 +32,7 @@ def home(): @classrooms.route('/classroom/', methods=['GET', 'POST']) @login_required def classroom(class_id): - current_class = Classroom.query.filter_by(id=class_id, teacher=current_user).first() # Making sure the class belongs to the current user + current_class = ClassroomModel.query.filter_by(id=class_id, teacher_model=current_user).first() # Making sure the class belongs to the current user if current_class is None: flash('Invalid class!', 'danger') return redirect(url_for('classrooms.home')) @@ -41,11 +41,31 @@ def classroom(class_id): if form.validate_on_submit(): # If form was submitted, creating report for the class description = "this is the best class" # TODO : add from the form file report_date = datetime.now().date() # TODO : add from the form file - students_df = pd.read_sql(Student.query.filter_by(class_id=class_id).statement, con=db.engine) - chat_df = create_chat_df(form.chat_file.data.stream) + students_df = pd.read_sql(StudentModel.query.filter_by(class_id=class_id).statement, con=db.engine) + chat_file = form.chat_file.data.stream.read().decode("utf-8").split("\n") + chat_df = create_chat_df(chat_file) + + report_object = Attendance(chat_df, students_df, ['name', "id_number", "phone"], form.time.data, form.start_sentence.data, ["ITC", "Tech", "Challenge"]) + + new_report = ReportModel(description=description, start_time=report_object.first_message_time, report_date=report_date, class_id=class_id) + db.session.add(new_report) + db.session.commit() + + # insert relevant data for each session to the database + for session_object in report_object.report_sessions: + session_table = SessionModel(start_time=session_object._first_message_time, report_id=new_report.id) + db.session.add(session_table) + db.session.commit() + + zoom_names_df = session_object.zoom_names_table(session_table.id) + zoom_names_df.to_sql('zoom_names_model', con=db.engine, if_exists="append", index=False) + + zoom_names_df = pd.read_sql(ZoomNamesModel.query.filter_by(session_id=session_table.id).statement, con=db.engine) + session_chat_df = session_object.chat_table(zoom_names_df) + session_chat_df.to_sql('chat_model', con=db.engine, if_exists="append", index=False) + + - my_class = Attendance(chat_df, students_df, ['name', "id_number", "phone"], form.time.data, form.start_sentence.data) - attendance_df, df_zoom_not_correct_list = my_class.get_attendance(["ITC", "Tech", "Challenge"]) return render_template("report.html") diff --git a/Server/classrooms/utils.py b/Server/classrooms/utils.py index 4ee5fe9..b5b82db 100644 --- a/Server/classrooms/utils.py +++ b/Server/classrooms/utils.py @@ -2,14 +2,13 @@ from datetime import datetime import pandas as pd -def create_chat_df(chat_stream): +def create_chat_df(chat_file): - chat_file = chat_stream.read().decode("utf-8").split("\n") regex_pattern = re.compile(r"(^\d{2}.\d{2}.\d{2})\s+From\s\s([\s\S]+)\s:\s([\s\S]+)") chat_content = [re.search(regex_pattern, line).groups() for line in chat_file if re.match(regex_pattern, line)] - chat_df = pd.DataFrame(chat_content, columns=["time", "users", "chat"]) - chat_df['chat'] = chat_df['chat'].str[:-1].astype(str) + chat_df = pd.DataFrame(chat_content, columns=["time", "zoom_name", "message"]) + chat_df['message'] = chat_df['message'].str[:-1].astype(str) chat_df["time"] = chat_df["time"].apply(lambda string: datetime.strptime(string, "%H:%M:%S")) return chat_df diff --git a/Server/models.py b/Server/models.py index a04db48..d47a8d3 100644 --- a/Server/models.py +++ b/Server/models.py @@ -4,29 +4,29 @@ @login_manager.user_loader def load_user(user_id): - return Teacher.query.get(int(user_id)) + return TeacherModel.query.get(int(user_id)) -class Teacher(db.Model, UserMixin): +class TeacherModel(db.Model, UserMixin): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(40), unique=True, nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) password = db.Column(db.String(60), nullable=False) # 60 chars because of the hashing algo - classrooms = db.relationship('Classroom', backref='teacher', lazy=True) + classrooms = db.relationship('ClassroomModel', backref='teacher_model', lazy=True) def __repr__(self): return f'Teacher({self.username}, {self.email}, {self.password})' -class Classroom(db.Model): +class ClassroomModel(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=False, nullable=False) - teacher_id = db.Column(db.Integer, db.ForeignKey('teacher.id'), nullable=False) - students = db.relationship('Student', backref='classroom', lazy=True) - reports = db.relationship('Report', backref='classroom', lazy=True) + teacher_id = db.Column(db.Integer, db.ForeignKey('teacher_model.id'), nullable=False) + students = db.relationship('StudentModel', backref='classroom_model', lazy=True) + reports = db.relationship('ReportModel', backref='classroom_model', lazy=True) -class Student(db.Model): +class StudentModel(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(70), unique=False, nullable=False) id_number = db.Column(db.String(10), unique=False, nullable=True) @@ -34,52 +34,43 @@ class Student(db.Model): gender = db.Column(db.Boolean, unique=False, nullable=True) # True means male phone = db.Column(db.Integer, unique=False, nullable=True) - class_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) - student_in_session = db.relationship('StudentInSession', backref='student', lazy=True) + class_id = db.Column(db.Integer, db.ForeignKey('classroom_model.id'), nullable=False) + zoom_names = db.relationship('ZoomNamesModel', backref='student_model', lazy=True) -class Report(db.Model): +class ReportModel(db.Model): id = db.Column(db.Integer, primary_key=True) description = db.Column(db.Text(), unique=False, nullable=True) start_time = db.Column(db.DateTime(), unique=False, nullable=False) # first timestamp of chat file report_date = db.Column(db.Date(), unique=False, nullable=True) # date of the report - given by the user - class_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) - sessions = db.relationship('Session', backref='report', lazy=True) + class_id = db.Column(db.Integer, db.ForeignKey('classroom_model.id'), nullable=False) + sessions = db.relationship('SessionModel', backref='report_model', lazy=True) -class Session(db.Model): +class SessionModel(db.Model): id = db.Column(db.Integer, primary_key=True) start_time = db.Column(db.DateTime(), unique=False, nullable=False) # first timestamp of chat session - report_id = db.Column(db.Integer, db.ForeignKey('report.id'), nullable=False) - student_in_session = db.relationship('StudentInSession', backref='session', lazy=True) + report_id = db.Column(db.Integer, db.ForeignKey('report_model.id'), nullable=False) + zoom_names = db.relationship('ZoomNamesModel', backref='session_model', lazy=True) -class ZoomNames(db.Model): - __tablename__ = 'zoom_names' +class ZoomNamesModel(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), unique=False, nullable=True) + session_id = db.Column(db.Integer, db.ForeignKey('session_model.id'), nullable=False) + student_id = db.Column(db.Integer, db.ForeignKey('student_model.id'), nullable=True) # if Null - means the student wasn't present in the session - chat = db.relationship('Chat', backref='zoom_names', lazy=True) - student_in_session = db.relationship('StudentInSession', backref='zoom_names', lazy=True) + chat = db.relationship('ChatModel', backref='zoom_names_model', lazy=True) -class Chat(db.Model): +class ChatModel(db.Model): id = db.Column(db.Integer, primary_key=True) time = db.Column(db.DateTime, unique=False, nullable=False) # time the message written message = db.Column(db.Text, unique=False, nullable=True) # message zoom user wrote relevant = db.Column(db.Boolean, unique=False, nullable=False) # True is message is part of the report - zoom_names_id = db.Column(db.Integer, db.ForeignKey('zoom_names.id'), nullable=False) - - -class StudentInSession(db.Model): - __tablename__ = 'student_in_session' - id = db.Column(db.Integer, primary_key=True) - - student_id = db.Column(db.Integer, db.ForeignKey('student.id'), nullable=False) - session_id = db.Column(db.Integer, db.ForeignKey('session.id'), nullable=False) - zoom_names_id = db.Column(db.Integer, db.ForeignKey('zoom_names.id'), nullable=False) + zoom_names_id = db.Column(db.Integer, db.ForeignKey('zoom_names_model.id'), nullable=False) diff --git a/Server/users/forms.py b/Server/users/forms.py index 68aaeb7..e2dd8aa 100644 --- a/Server/users/forms.py +++ b/Server/users/forms.py @@ -1,7 +1,7 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField, BooleanField from wtforms.validators import DataRequired, EqualTo, Length, Email, ValidationError -from Server.models import Teacher +from Server.models import TeacherModel class RegistrationForm(FlaskForm): @@ -12,14 +12,14 @@ class RegistrationForm(FlaskForm): submit = SubmitField('Sign Up') def validate_username(self, username): - user = Teacher.query.filter_by(username=username.data).first() + user = TeacherModel.query.filter_by(username=username.data).first() if user: print(user) raise ValidationError('Username already taken.') # Add special signs validations def validate_email(self, email): - user = Teacher.query.filter_by(email=email.data).first() + user = TeacherModel.query.filter_by(email=email.data).first() if user: raise ValidationError('Email already taken.') diff --git a/Server/users/routes.py b/Server/users/routes.py index f320f70..7418fdf 100644 --- a/Server/users/routes.py +++ b/Server/users/routes.py @@ -1,7 +1,7 @@ from flask import render_template, redirect, url_for, flash, Blueprint from Server import bcrypt, db from Server.users.forms import LoginForm, RegistrationForm -from Server.models import Teacher +from Server.models import TeacherModel from flask_login import login_user, current_user, logout_user, login_required users = Blueprint('users', __name__) @@ -13,8 +13,8 @@ def login(): form = LoginForm() if form.validate_on_submit(): - user = Teacher.query.filter_by(email=form.auth.data).first() or \ - Teacher.query.filter_by(username=form.auth.data).first() # User can be validated with both username and email + user = TeacherModel.query.filter_by(email=form.auth.data).first() or \ + TeacherModel.query.filter_by(username=form.auth.data).first() # User can be validated with both username and email if user and bcrypt.check_password_hash(user.password, form.password.data): login_user(user, remember=form.remember.data) return redirect(url_for('classrooms.home')) @@ -32,7 +32,7 @@ def register(): form = RegistrationForm() if form.validate_on_submit(): hashed_password = bcrypt.generate_password_hash((form.password.data)).decode('utf-8') - user = Teacher( + user = TeacherModel( username=form.username.data, password=hashed_password, email=form.email.data From ff025e9fab16f6b8d41e41a5bee89703ab4389cc Mon Sep 17 00:00:00 2001 From: Inbar Shirizly Date: Fri, 2 Oct 2020 18:33:56 +0300 Subject: [PATCH 9/9] Aranged server folder --- .gitignore | 11 ----------- Server/{ => Server}/__init__.py | 0 Server/{ => Server}/classrooms/__init__.py | 0 Server/{ => Server}/classrooms/attendance_check.py | 1 + Server/{ => Server}/classrooms/forms.py | 0 .../{ => Server}/classrooms/loading_classroom_file.py | 0 Server/{ => Server}/classrooms/routes.py | 0 Server/{ => Server}/classrooms/utils.py | 0 Server/{ => Server}/config.py | 0 Server/{ => Server}/models.py | 0 Server/{ => Server}/templates/_render_form_field.html | 0 Server/{ => Server}/templates/classroom.html | 0 Server/{ => Server}/templates/home.html | 0 Server/{ => Server}/templates/layout.html | 0 Server/{ => Server}/templates/login.html | 0 Server/{ => Server}/templates/register.html | 0 Server/{ => Server}/templates/report.html | 0 Server/{ => Server}/users/__init__.py | 0 Server/{ => Server}/users/forms.py | 0 Server/{ => Server}/users/routes.py | 0 requirements.txt => Server/requirements.txt | 0 run.py => Server/run.py | 0 22 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 .gitignore rename Server/{ => Server}/__init__.py (100%) rename Server/{ => Server}/classrooms/__init__.py (100%) rename Server/{ => Server}/classrooms/attendance_check.py (99%) rename Server/{ => Server}/classrooms/forms.py (100%) rename Server/{ => Server}/classrooms/loading_classroom_file.py (100%) rename Server/{ => Server}/classrooms/routes.py (100%) rename Server/{ => Server}/classrooms/utils.py (100%) rename Server/{ => Server}/config.py (100%) rename Server/{ => Server}/models.py (100%) rename Server/{ => Server}/templates/_render_form_field.html (100%) rename Server/{ => Server}/templates/classroom.html (100%) rename Server/{ => Server}/templates/home.html (100%) rename Server/{ => Server}/templates/layout.html (100%) rename Server/{ => Server}/templates/login.html (100%) rename Server/{ => Server}/templates/register.html (100%) rename Server/{ => Server}/templates/report.html (100%) rename Server/{ => Server}/users/__init__.py (100%) rename Server/{ => Server}/users/forms.py (100%) rename Server/{ => Server}/users/routes.py (100%) rename requirements.txt => Server/requirements.txt (100%) rename run.py => Server/run.py (100%) diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 3993617..0000000 --- a/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -*.pyc -*.log -.idea -meeting* -*.vscode -.ipynb_checkpoints -*.xlsx -checking_parsing.ipynb -Server/site.db -site.db -*.xls diff --git a/Server/__init__.py b/Server/Server/__init__.py similarity index 100% rename from Server/__init__.py rename to Server/Server/__init__.py diff --git a/Server/classrooms/__init__.py b/Server/Server/classrooms/__init__.py similarity index 100% rename from Server/classrooms/__init__.py rename to Server/Server/classrooms/__init__.py diff --git a/Server/classrooms/attendance_check.py b/Server/Server/classrooms/attendance_check.py similarity index 99% rename from Server/classrooms/attendance_check.py rename to Server/Server/classrooms/attendance_check.py index 92b78a6..a1bc161 100644 --- a/Server/classrooms/attendance_check.py +++ b/Server/Server/classrooms/attendance_check.py @@ -43,6 +43,7 @@ def get_df_of_time_segment(df, start_index, time_delta): def report_sessions(self): return self._sessions + class Session: def __init__(self, df_students, df_session_chat, filter_modes, not_included_zoom_users): diff --git a/Server/classrooms/forms.py b/Server/Server/classrooms/forms.py similarity index 100% rename from Server/classrooms/forms.py rename to Server/Server/classrooms/forms.py diff --git a/Server/classrooms/loading_classroom_file.py b/Server/Server/classrooms/loading_classroom_file.py similarity index 100% rename from Server/classrooms/loading_classroom_file.py rename to Server/Server/classrooms/loading_classroom_file.py diff --git a/Server/classrooms/routes.py b/Server/Server/classrooms/routes.py similarity index 100% rename from Server/classrooms/routes.py rename to Server/Server/classrooms/routes.py diff --git a/Server/classrooms/utils.py b/Server/Server/classrooms/utils.py similarity index 100% rename from Server/classrooms/utils.py rename to Server/Server/classrooms/utils.py diff --git a/Server/config.py b/Server/Server/config.py similarity index 100% rename from Server/config.py rename to Server/Server/config.py diff --git a/Server/models.py b/Server/Server/models.py similarity index 100% rename from Server/models.py rename to Server/Server/models.py diff --git a/Server/templates/_render_form_field.html b/Server/Server/templates/_render_form_field.html similarity index 100% rename from Server/templates/_render_form_field.html rename to Server/Server/templates/_render_form_field.html diff --git a/Server/templates/classroom.html b/Server/Server/templates/classroom.html similarity index 100% rename from Server/templates/classroom.html rename to Server/Server/templates/classroom.html diff --git a/Server/templates/home.html b/Server/Server/templates/home.html similarity index 100% rename from Server/templates/home.html rename to Server/Server/templates/home.html diff --git a/Server/templates/layout.html b/Server/Server/templates/layout.html similarity index 100% rename from Server/templates/layout.html rename to Server/Server/templates/layout.html diff --git a/Server/templates/login.html b/Server/Server/templates/login.html similarity index 100% rename from Server/templates/login.html rename to Server/Server/templates/login.html diff --git a/Server/templates/register.html b/Server/Server/templates/register.html similarity index 100% rename from Server/templates/register.html rename to Server/Server/templates/register.html diff --git a/Server/templates/report.html b/Server/Server/templates/report.html similarity index 100% rename from Server/templates/report.html rename to Server/Server/templates/report.html diff --git a/Server/users/__init__.py b/Server/Server/users/__init__.py similarity index 100% rename from Server/users/__init__.py rename to Server/Server/users/__init__.py diff --git a/Server/users/forms.py b/Server/Server/users/forms.py similarity index 100% rename from Server/users/forms.py rename to Server/Server/users/forms.py diff --git a/Server/users/routes.py b/Server/Server/users/routes.py similarity index 100% rename from Server/users/routes.py rename to Server/Server/users/routes.py diff --git a/requirements.txt b/Server/requirements.txt similarity index 100% rename from requirements.txt rename to Server/requirements.txt diff --git a/run.py b/Server/run.py similarity index 100% rename from run.py rename to Server/run.py