├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── classroom_manager ├── __init__.py ├── forms.py ├── models.py ├── network.py ├── routes.py ├── site.sqlite3 ├── static │ ├── css │ │ ├── home.css │ │ └── styles.css │ ├── imgs │ │ ├── 1_vYtDyAbOMTJnArJvXix5wg.jpeg │ │ ├── 7bbcc1707ce09218fa84866a65b3c871.png │ │ ├── 800px_COLOURBOX4159486.jpg │ │ ├── Periodic_Table.png │ │ ├── call-icon.png │ │ ├── chat-icon.png │ │ ├── default_class.png │ │ ├── defaultuser-icon.png │ │ ├── dropdown-icon.png │ │ ├── gear-icon.png │ │ ├── group-icon.png │ │ ├── meetings-icon.png │ │ ├── notification-icon.png │ │ ├── plus-icon.png │ │ ├── storage-nand_nor_gates-f_mobile.png │ │ ├── trig.gif │ │ └── website_icon.png │ ├── js │ │ ├── app.js │ │ ├── chat.js │ │ └── classroom.js │ └── submissions │ │ └── homeworktest.txt ├── templates │ ├── activity.html │ ├── app.html │ ├── app_layout.html │ ├── calls.html │ ├── chats.html │ ├── classrooms.html │ ├── home.html │ ├── layout.html │ ├── login.html │ ├── meetings.html │ └── register.html └── utils.py ├── requirements.txt ├── run.py └── screenshots ├── screenshot1.PNG ├── screenshot2.PNG ├── screenshot3.PNG ├── screenshot4.PNG ├── screenshot5.PNG └── screenshot6.PNG /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 adri711 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 | 6 | 7 | Languages 9 | 10 | Top_Language 12 |

13 | 14 | # Classroom Manager 15 | 16 | ## Description 17 | Hey there! This is a website called Classroom Manager. You can create classrooms, have fellow students join the classrooms, chat with your peers, create notes, assign assignments, and more! You can dm 'Top Of Tech#4867' or 'adri711#7835' on discord if you have any thoughts. 18 | 19 | Created by [Top Of Tech](http:www.github.com/Top-Of-Tech) and [adri711](http:www.github.com/adri711) 20 | 21 | 22 | ## Instructions 23 | 24 | NOTE: Make sure to run all commands in the same directory as the files! 25 | 26 | 1. Install requirements 27 | Run this command in your terminal or command prompt: `pip install -r requirements.txt` 28 | 29 | 2. Run the website 30 | Run this command: `python run.py` 31 | 32 | 3. Go to the website 33 | Go to this url: [http://127.0.0.7:5000](http://127.0.0.7:5000) 34 | 35 | 4. Register 36 | 37 | 5. Sign in with your email and password 38 | 39 | 6. Explore the app! 40 | -------------------------------------------------------------------------------- /classroom_manager/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, url_for 2 | from flask_sqlalchemy import SQLAlchemy 3 | from flask_bcrypt import Bcrypt 4 | from flask_login import LoginManager 5 | from flask_socketio import SocketIO 6 | from os.path import join, dirname, realpath 7 | 8 | app = Flask(__name__) 9 | app.config['SECRET_KEY'] = 'R\x1a^-\x14?mq\x1ce\xf7\xefi\xb7\x0e\xa0\x02\x0c\xd6-$\x033\xc4' 10 | app.config['IMAGE_UPLOADS'] = join(dirname(realpath(__file__)), 'static/imgs/') 11 | app.config['FILE_UPLOADS'] = join(dirname(realpath(__file__)), 'static/submissions/') 12 | app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False 13 | app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.sqlite3' 14 | db = SQLAlchemy(app) 15 | socketio = SocketIO(app, cors_allowed_origins="*") 16 | 17 | bcrypt = Bcrypt(app) 18 | login_manager = LoginManager(app) 19 | login_manager.login_view = 'login' 20 | 21 | #modules import 22 | from classroom_manager.routes import * 23 | from classroom_manager.models import User 24 | from classroom_manager.network import * 25 | #db.create_all() 26 | -------------------------------------------------------------------------------- /classroom_manager/forms.py: -------------------------------------------------------------------------------- 1 | from flask_wtf import FlaskForm 2 | from wtforms import StringField, PasswordField, SubmitField, BooleanField, RadioField 3 | from wtforms.validators import DataRequired, Length, Email, EqualTo, InputRequired, ValidationError 4 | from classroom_manager.models import User 5 | 6 | class RegistrationForm(FlaskForm): # Form objects for the front-end devs 7 | name = StringField("Name", 8 | validators=[DataRequired(), Length(min=2, max=25)]) 9 | last_name = StringField("Last Name", 10 | validators=[DataRequired(), Length(min=2, max=25)]) 11 | username = StringField("Username", 12 | validators=[DataRequired(), Length(min=2, max=25)]) 13 | email = StringField("Email", 14 | validators=[DataRequired(), Email()]) 15 | password = PasswordField("Password", 16 | validators=[DataRequired()]) 17 | confirm_password = PasswordField("Confirm Password", 18 | validators=[DataRequired(), EqualTo('password')]) 19 | student_or_teacher = RadioField("Teacher or student", 20 | choices=[('teacher', 'Teacher'), ('student', 'Student')], 21 | validators=[InputRequired()]) 22 | submit = SubmitField('Sign Up') 23 | 24 | def validate_username(self, username): 25 | if User.query.filter_by(username=username.data).first(): # Checks if a user with the same username is in the database 26 | raise ValidationError("That username is taken, please choose a different one.") 27 | 28 | def validate_email(self, email): 29 | if User.query.filter_by(email=email.data).first(): # Checks if a user with the same username is in the database 30 | raise ValidationError("That email is taken, please choose a different one.") 31 | 32 | class LoginForm(FlaskForm): # Form objects for the front-end devs 33 | email = StringField("Email", 34 | validators=[DataRequired(), Email()]) 35 | password = PasswordField("Password", 36 | validators=[DataRequired()]) 37 | remember = BooleanField("Remember Me") 38 | submit = SubmitField('Log In') 39 | -------------------------------------------------------------------------------- /classroom_manager/models.py: -------------------------------------------------------------------------------- 1 | from flask_sqlalchemy import SQLAlchemy 2 | from classroom_manager import login_manager, db 3 | from flask_login import UserMixin 4 | from datetime import datetime 5 | 6 | @login_manager.user_loader 7 | def load_user(user_id): 8 | return User.query.get(int(user_id)) 9 | 10 | class User(db.Model, UserMixin): 11 | id = db.Column(db.Integer, primary_key=True) 12 | first_name = db.Column(db.String(25), nullable=False) 13 | last_name = db.Column(db.String(25), nullable=False) 14 | username = db.Column(db.String(20), unique=True, nullable=False) 15 | email = db.Column(db.String(254), unique=True, nullable=False) 16 | image_file = db.Column(db.String(20), nullable=False, default="default.jpg") 17 | status = db.Column(db.String(10), nullable=False) 18 | password = db.Column(db.String(100), nullable=False) 19 | notes = db.relationship('Note', backref='author', lazy=True) 20 | assignments = db.relationship('Assignment', backref='author', lazy=True) 21 | messages = db.relationship('Message', backref='author', lazy=True) 22 | membership = db.relationship('Membership', backref='classroom', lazy=True) 23 | submission = db.relationship('AssignmentSubmission', backref='submission', lazy=True) 24 | def __repr__(self): 25 | return f"User(username: '{self.username}', email: '{self.email}', profile_img: '{self.image_file}')" 26 | 27 | 28 | class Classroom(db.Model): 29 | id = db.Column(db.Integer, primary_key=True) 30 | code = db.Column(db.String(60), nullable=True) 31 | image_file = db.Column(db.String(42), nullable=False, default="default_class.png") 32 | name = db.Column(db.String(25), nullable=False) 33 | description = db.Column(db.String(262), nullable=True) 34 | membership = db.relationship('Membership', backref='membership', lazy=True) 35 | 36 | def __repr__(self): 37 | return f"Classroom(name: '{self.name}', class_img: '{self.image_file}'" 38 | 39 | class Membership(db.Model): 40 | user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) 41 | classroom_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) 42 | role = db.Column(db.String(7), nullable=False) 43 | 44 | __table_args__ = ( 45 | db.PrimaryKeyConstraint(user_id, classroom_id), # Allows two primary keys 46 | ) 47 | 48 | def __repr__(self): 49 | return f"Membership(user_id: '{self.user_id}', classroom_id: '{self.classroom_id}')" 50 | 51 | class Message(db.Model): 52 | id = db.Column(db.Integer, primary_key=True) 53 | author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) 54 | channel_id = db.Column(db.Integer, db.ForeignKey('channel.id'), nullable=False) 55 | contents = db.Column(db.String(100), nullable=False) 56 | ischild = db.Column(db.Integer, default=-1, nullable=False) # if this is set to -1 then this message is not a reply 57 | date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) 58 | 59 | def __repr__(self): 60 | return f"Message(author_id: '{self.author_id}', classroom: '{self.classroom}')" 61 | 62 | class Note(db.Model): 63 | id = db.Column(db.Integer, primary_key=True) 64 | author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) 65 | channel_id = db.Column(db.Integer, db.ForeignKey('channel.id'), nullable=False) 66 | title = db.Column(db.String(32), nullable=False) 67 | note_text = db.Column(db.String(300), nullable=False) 68 | note_imgs = db.Column(db.String(52), nullable=True) 69 | 70 | def __repr__(self): 71 | return f"Note(author_id: '{self.author_id}', classroom: '{self.classroom}')" 72 | 73 | class Assignment(db.Model): 74 | id = db.Column(db.Integer, primary_key=True) 75 | author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) 76 | channel_id = db.Column(db.Integer, db.ForeignKey('channel.id'), nullable=False) 77 | assignment_text = db.Column(db.String(200), nullable=False) 78 | due_date = db.Column(db.DateTime, nullable=False) 79 | submission = db.relationship('AssignmentSubmission', backref='assignment', lazy=True) 80 | def __repr__(self): 81 | return f"Assignment(author_id: '{self.author_id}', classroom: '{self.classroom}')" 82 | 83 | 84 | class Channel(db.Model): 85 | id = db.Column(db.Integer, primary_key=True) 86 | classroom_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) 87 | name = db.Column(db.String(30), nullable=False) 88 | message = db.relationship('Message', backref='mesage', lazy=True) 89 | note = db.relationship('Note', backref='note', lazy=True) 90 | assignment = db.relationship('Assignment', backref='assignment', lazy=True) 91 | 92 | def __repr__(self): 93 | return f"Channel(classroom: '{self.classroom_id}', name: '{self.name}')" 94 | 95 | class Ban(db.Model): #hasnt been fully implemented yet 96 | id = db.Column(db.Integer, primary_key=True) 97 | classroom_id = db.Column(db.Integer, db.ForeignKey('classroom.id'), nullable=False) 98 | def __repr__(self): 99 | return f"Ban(classroom: '{self.classroom_id}', user_id: '{self.user_id}')" 100 | 101 | class DirectMessage(db.Model): 102 | id = db.Column(db.Integer, primary_key=True) 103 | sender_id = db.Column(db.Integer, nullable=False) 104 | receiver_id = db.Column(db.Integer, nullable=False) 105 | content = db.Column(db.String(100), nullable=False) 106 | date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) 107 | 108 | def __repr__(self): 109 | return f"Message(author_id: '{self.sender_id}')" 110 | 111 | 112 | class AssignmentSubmission(db.Model): 113 | id = db.Column(db.Integer, primary_key=True) 114 | user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) 115 | assignment_id = db.Column(db.Integer, db.ForeignKey('assignment.id'), nullable=False) 116 | file_location = db.Column(db.String(52), nullable=True) 117 | 118 | def __repr__(self): 119 | return f'submission({id})' -------------------------------------------------------------------------------- /classroom_manager/network.py: -------------------------------------------------------------------------------- 1 | from flask import session, redirect, url_for,request 2 | from flask_login import current_user 3 | from flask_socketio import SocketIO, emit, send, disconnect, join_room, leave_room 4 | from classroom_manager import socketio 5 | from classroom_manager.models import User, Classroom, Membership, Channel, Message, Note, DirectMessage 6 | from classroom_manager.utils import generate_code 7 | from classroom_manager import db 8 | from datetime import datetime 9 | 10 | users = {} 11 | 12 | @socketio.on('connect') 13 | def handle_connections(): 14 | if not current_user.is_authenticated: 15 | disconnect() 16 | return redirect(url_for('login')) 17 | user = User.query.filter(User.id==current_user.get_id()).first() 18 | users[user.username] = request.sid 19 | user_memberships = Membership.query.filter(Membership.user_id==current_user.get_id()) 20 | [join_room(str(user_membership.classroom_id)) for user_membership in user_memberships] #Instead of making a room for every channel we will make a room for every classroom 21 | 22 | @socketio.on('disconnect') 23 | def handle_disconnections(): 24 | if not current_user.is_authenticated: 25 | disconnect() 26 | return redirect(url_for('login')) 27 | 28 | @socketio.on('channel_conversation') 29 | def handle_conversations(data): 30 | if current_user.is_authenticated: 31 | if len(data['message']): 32 | sender_id = current_user.get_id() 33 | sender = User.query.filter(User.id==sender_id).first() 34 | #broadcastting message to users in this room 35 | room = Channel.query.filter(Channel.id==data['channel_id']).first() 36 | if Membership.query.filter(Membership.user_id==sender_id, Membership.classroom_id==room.classroom_id).first(): 37 | new_message = Message(author_id=sender_id, channel_id=data['channel_id'], contents=data['message']) 38 | emit('channel_conversation', {'content': data['message'], 'date': 'just now', 'author': {'id':sender_id, 'name': sender.first_name + ' ' + sender.last_name}, 'channel_id': data['channel_id']}, room=str(room.classroom_id)) 39 | #Saving the message in the db 40 | db.session.add(new_message) #adding message 41 | db.session.commit() 42 | else: 43 | socket.emit('error', 'message must have more than one character.') 44 | 45 | else: 46 | disconnect() 47 | return redirect(url_for('login')) 48 | 49 | @socketio.on('join-room') 50 | def joinroom(room_id): 51 | if current_user.is_authenticated: 52 | join_room(str(room_id)) 53 | 54 | @socketio.on('channel_action') 55 | def channel_action(data): 56 | membership = Membership.query.filter(Membership.user_id==current_user.get_id(), Membership.classroom_id==data['classroom_id']).first() 57 | if membership.role == 'super': 58 | if data['action'] == 'add': 59 | new_channel = Channel(classroom_id=int(data['classroom_id']),name=data['name_input']) 60 | db.session.add(new_channel) 61 | db.session.flush() 62 | db.session.commit() 63 | emit('new_channel', {'classroom_id': data['classroom_id'],'name': new_channel.name, 'id': new_channel.id},room=str(data['classroom_id'])) 64 | elif data['action'] == 'rename': 65 | specified_channel = Channel.query.filter(Channel.id==data['channel_id']).first() 66 | specified_channel.name = data['name_input'] 67 | db.session.commit() 68 | emit('channel_rename', {'classroom_id':data['classroom_id'],'new_name': data['name_input'], 'id': data['channel_id']}, room=str(data['classroom_id'])) 69 | elif data['action'] == 'delete': 70 | specified_channel = Channel.query.filter(Channel.id==data['channel_id']).delete() 71 | Message.query.filter(Message.channel_id==data['channel_id']).delete() 72 | Note.query.filter(Note.channel_id==data['channel_id']).delete() 73 | db.session.commit() 74 | emit('channel_delete', {'classroom_id':data['classroom_id'],'id': data['channel_id']}, room=str(data['classroom_id'])) 75 | else: 76 | pass 77 | 78 | @socketio.on('code_regeneration_req') 79 | def regenerate_code(data): 80 | Classroom.query.filter(Classroom.id==data['classroom_id']).first().code=generate_code(data['classroom_id']) 81 | db.session.commit() 82 | emit('code_regeneration', {'classroom_id': data['classroom_id'], 'code':Classroom.query.filter(Classroom.id==data['classroom_id']).first().code}) 83 | 84 | @socketio.on('user_action') 85 | def user_action(data): 86 | membership = Membership.query.filter(Membership.user_id==current_user.get_id(), Membership.classroom_id==data['classroom_id']).first() 87 | if membership.role == 'super': 88 | if data['action'] == 'kick': 89 | Membership.query.filter(Membership.classroom_id==int(data['classroom_id']), Membership.user_id==int(data['user_id'])).delete() 90 | db.session.commit() 91 | emit('user_kick', {'user_id': data['user_id'], 'classroom_id': data['classroom_id']}, room=str(data['classroom_id'])) 92 | elif data['action'] == 'ban': 93 | pass 94 | 95 | @socketio.on('classroom_leave') 96 | def user_leave(data): 97 | if current_user.is_authenticated: 98 | Membership.query.filter(Membership.user_id==current_user.get_id(), Membership.classroom_id==data['classroom_id']).delete() 99 | db.session.commit() 100 | 101 | @socketio.on('direct_message') 102 | def direct_message(data): 103 | new_direct_message = DirectMessage(sender_id=current_user.get_id(), receiver_id=data['to'], content=data['message']) 104 | db.session.add(new_direct_message) 105 | db.session.commit() 106 | recipient_username = User.query.filter(User.id==data['to']).first().username 107 | if recipient_username in users.keys(): 108 | emit('direct_message', {'content': data['message'], 'author': User.query.filter(User.id==current_user.get_id()).first().username, 'date': 'Just now'}, room=users[recipient_username]) 109 | emit('direct_message', {'content': data['message'], 'author': User.query.filter(User.id==current_user.get_id()).first().username, 'date': 'Just now'}, room=users[User.query.filter(User.id==current_user.get_id()).first().username]) -------------------------------------------------------------------------------- /classroom_manager/routes.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, render_template, url_for, flash, redirect, request, jsonify 2 | from classroom_manager.forms import RegistrationForm, LoginForm 3 | from classroom_manager import app, bcrypt, db, socketio 4 | from classroom_manager.models import User, Classroom, Membership, Channel, Message, Note, Assignment, DirectMessage, AssignmentSubmission 5 | from flask_login import login_user, current_user, logout_user, login_required 6 | from classroom_manager.utils import generate_code 7 | from werkzeug.utils import secure_filename 8 | from types import SimpleNamespace 9 | from dateutil import parser 10 | import os 11 | 12 | @app.route("/") 13 | @app.route("/home") 14 | def home(): 15 | return render_template("home.html", title='Home Page') 16 | 17 | @app.route("/login", methods=["GET", "POST"]) # Make sure the form tag has the method, "POST" 18 | def login(): 19 | if current_user.is_authenticated: 20 | return redirect(url_for("home")) 21 | form = LoginForm() 22 | if form.validate_on_submit(): 23 | user = User.query.filter_by(email=form.email.data).first() 24 | if user and bcrypt.check_password_hash(user.password, form.password.data): 25 | login_user(user, remember=form.remember.data) 26 | next_page = request.args.get('next') 27 | flash(f"Successfully logged in!", 'success') 28 | return redirect(next_page) if next_page else redirect(url_for('__app__')) 29 | else: 30 | flash("Login unsuccessful, please try again.", 'danger') 31 | return render_template("login.html", form=form, title='Login') 32 | 33 | @app.route("/register", methods=["GET", "POST"]) # Make sure the form tag has the method, "POST" 34 | def register(): 35 | if current_user.is_authenticated: 36 | return redirect(url_for("__app__")) 37 | form = RegistrationForm() 38 | if form.validate_on_submit(): 39 | hashed_password = bcrypt.generate_password_hash(form.password.data).decode("utf-8") # Hash the password 40 | new_user = User(first_name=form.name.data, last_name=form.last_name.data, username=form.username.data, 41 | email=form.email.data, status=form.student_or_teacher.data, password=hashed_password) 42 | db.session.add(new_user) # Add the new user to the database queue 43 | db.session.commit() # Commit all the changes in the queue 44 | return redirect(url_for('login')) 45 | return render_template("register.html", form=form, title='Register') 46 | 47 | @app.route("/logout") 48 | def logout(): 49 | logout_user() 50 | return redirect(url_for("home")) 51 | 52 | @app.route("/app") 53 | @login_required 54 | def __app__(): 55 | return render_template("app.html", title='Application') 56 | 57 | @app.route('/app/chats', methods=['POST']) 58 | def app_chats(): 59 | users, contacts = set(), [] 60 | messages = DirectMessage.query.filter((DirectMessage.receiver_id==current_user.get_id()) | (DirectMessage.sender_id==current_user.get_id())).all() 61 | [users.add(message.receiver_id) if int(message.receiver_id) != int(current_user.get_id()) else users.add(int(message.sender_id)) for message in messages] 62 | for user in User.query.filter(User.id.in_(users)).all(): 63 | contact = SimpleNamespace() 64 | contact.id = user.id 65 | contact.name = user.username 66 | contacts.append(contact) 67 | return render_template('chats.html', title='Contacts', data=contacts, type='contact') 68 | 69 | @app.route('/add-contact', methods=['POST']) 70 | def add_contact(): 71 | contact = User.query.filter(User.username==request.form['username']).first() 72 | if contact and int(contact.id) == int(current_user.get_id()): 73 | contact = None # 74 | return jsonify({'name': contact.username, 'id': contact.id, 'img_url': url_for('static', filename='imgs/defaultuser-icon.png')}) if contact and contact.id != current_user.get_id() else jsonify({'error': 'user not found.'}) 75 | 76 | @app.route('/retrieve-directmessages/', methods=['POST']) 77 | def retrieve_directmessages(user_id): 78 | messages = DirectMessage.query.filter((DirectMessage.sender_id==user_id) & (DirectMessage.receiver_id==current_user.get_id()) | (DirectMessage.sender_id==current_user.get_id()) & (DirectMessage.receiver_id==user_id)) 79 | return jsonify({'messages': [{'content': message.content, 'date': message.date, 'author': User.query.filter(User.id==message.sender_id).first().username, 'mein': int(current_user.get_id())==(message.sender_id)} for message in messages]}) 80 | 81 | ''' 82 | @app.route('/retrieve-contacts', methods=['POST']) 83 | def retrieve_contacts(): 84 | pass''' 85 | 86 | @app.route('/app/classrooms', methods=['POST']) 87 | def app_classrooms(): 88 | memberships = Membership.query.filter(Membership.user_id==current_user.get_id()).all() 89 | classrooms = Classroom.query.filter(Classroom.id.in_([membership.classroom_id for membership in memberships])).all() 90 | return render_template('classrooms.html', title='Classrooms', data=classrooms, type='classroom') 91 | 92 | @app.route('/add-note', methods=['POST']) 93 | def add_note(): 94 | if request.form['note_title'] and request.form['note_text'] and request.form['channel_id']: 95 | note_title = request.form['note_title'] 96 | note_text = request.form['note_text'] 97 | note_channel_id = request.form['channel_id'] 98 | if request.files["note_image"]: 99 | image = request.files["note_image"] 100 | filename = secure_filename(image.filename) 101 | image.save(os.path.join(app.config['IMAGE_UPLOADS'], filename)) 102 | new_note = Note(author_id=current_user.get_id(), title=note_title, note_text=note_text,note_imgs=filename, channel_id=note_channel_id) 103 | db.session.add(new_note) 104 | db.session.commit() 105 | return jsonify({'new_note': {'note_title':new_note.title, 'note_id': new_note.id, 'note_text': new_note.note_text, 'note_img': url_for('static', filename='imgs/' + new_note.note_imgs)}}) 106 | else: 107 | pass 108 | 109 | @app.route('/classroom-settings/', methods=['POST']) 110 | def classroom_settings(classroom_id): 111 | specified_classroom = Classroom.query.filter(Classroom.id==classroom_id).first() 112 | if specified_classroom: 113 | membership = Membership.query.filter(Membership.classroom_id==classroom_id, Membership.user_id==current_user.get_id()).first() 114 | members = Membership.query.filter(Membership.classroom_id==classroom_id) 115 | members = User.query.filter(User.id.in_([member.user_id for member in members])) 116 | permission = membership.role == 'super' 117 | return jsonify({'room_code': specified_classroom.code, 118 | 'permission': permission, 119 | 'members': [{'name': member.first_name + ' ' + member.last_name, 120 | 'id': member.id} for member in members if int(member.id) != int(current_user.get_id())], 121 | 'channels': [{'id': channel.id, 'name': channel.name} for channel in Channel.query.filter(Channel.classroom_id==classroom_id)]}) 122 | return jsonify({'error': 'Classroom wasn\'t found.'}) 123 | 124 | @app.route('/create-team', methods=['POST']) 125 | def create_team(): 126 | #getting data from the form 127 | team_name = request.form['team_name'] 128 | team_description = request.form['team_description'] 129 | if team_name: 130 | #creating the classroom 131 | new_classroom = Classroom(name=team_name, description=team_description) 132 | db.session.add(new_classroom) #adding the new classroom 133 | db.session.flush() #using flush just so we can get the id of the classroom 134 | new_classroom.code = generate_code(new_classroom.id) 135 | db.session.flush() 136 | new_channel = Channel(name="General", classroom_id=new_classroom.id) #creating general channel for the classroom 137 | db.session.add(new_channel) 138 | db.session.flush() #same here... 139 | #Creating membership for the user and classroom 140 | new_membership = Membership(user_id=current_user.get_id(), classroom_id=new_classroom.id, role='super') 141 | db.session.add(new_membership) 142 | db.session.commit() 143 | #sending back the classroom object 144 | return jsonify({'new_classroom': { 145 | 'name': new_classroom.name, 146 | 'id': new_classroom.id, 147 | 'imgurl': url_for('static', filename="imgs/" + new_classroom.image_file) 148 | }, 149 | 'channel': new_channel.id 150 | }) 151 | return jsonify({'error': 'given name is invalid.'}) #Returning an error if the given name is empty 152 | 153 | 154 | @app.route('/join-team', methods=['POST']) 155 | def join_team(): 156 | team_code = request.form['code'] 157 | if team_code: 158 | classroom_id = Classroom.query.filter(Classroom.code==team_code).first() 159 | if classroom_id: 160 | new_membership = Membership(user_id=current_user.get_id(), classroom_id=classroom_id.id, role='regular') 161 | db.session.add(new_membership) 162 | db.session.commit() 163 | return jsonify({'result': {'id': classroom_id.id, 164 | 'name': classroom_id.name}, 165 | 'url_for_img': url_for('static', filename='imgs/'+ classroom_id.image_file)}) 166 | return jsonify({'error': "given code has either expired or is not valid"}) 167 | return jsonify({'error': 'given code is invalid'}) 168 | 169 | @app.route('/retrieve-notes/', methods=['POST']) 170 | def retrieve_notes(channel): 171 | notes = Note.query.filter(Note.channel_id==channel) 172 | return jsonify([{'author_id': note.author_id, 'note_title': note.title,'note_id': note.id,'note_text': note.note_text, 'note_img': url_for('static', filename='imgs/'+note.note_imgs) if note.note_imgs else None} for note in notes]) 173 | 174 | @app.route('/retrieve-assignments/', methods=['POST']) 175 | def retrieve_assignments(channel): 176 | assignments = Assignment.query.filter(Assignment.channel_id==channel) 177 | role = Membership.query.filter(Membership.classroom_id==Channel.query.filter(Channel.id==channel).first().classroom_id, Membership.user_id==current_user.get_id()).first().role 178 | return jsonify({'role': role, 'assignments': [{'id': assignment.id, 'text': assignment.assignment_text, 'duedate': assignment.due_date, 'submission_state': AssignmentSubmission.query.filter(assignment.id==AssignmentSubmission.assignment_id, AssignmentSubmission.user_id==current_user.get_id()).first() is not None} for assignment in assignments]}) 179 | 180 | @app.route('/add-assignment', methods=['POST']) 181 | def add_assignment(): 182 | get_date = parser.parse(request.form['assignment_date']) 183 | new_assignment = Assignment(author_id=current_user.get_id(), due_date=get_date, assignment_text=request.form['assignment_text'], channel_id=request.form['channel_id']) 184 | db.session.add(new_assignment) 185 | db.session.commit() 186 | return jsonify({'id': new_assignment.id, 'text': new_assignment.assignment_text, 'duedate': new_assignment.due_date}) 187 | 188 | @app.route('/homework-submit', methods=['POST']) 189 | def homework_submit(): 190 | if request.files["homework"]: 191 | homework_file = request.files["homework"] 192 | filename = secure_filename(homework_file.filename) 193 | homework_file.save(os.path.join(app.config['FILE_UPLOADS'], filename)) 194 | new_submission = AssignmentSubmission(user_id=current_user.get_id(),assignment_id=request.form['assignment_id'], file_location=filename) 195 | db.session.add(new_submission) 196 | db.session.commit() 197 | return jsonify({'message': 'successfuly submitted'}) 198 | 199 | @app.route('/retrieve-submissions/', methods=['POST']) 200 | def retrieve_submissions(assignment_id): 201 | assignments = AssignmentSubmission.query.filter(AssignmentSubmission.assignment_id==assignment_id) 202 | return jsonify([{'name': User.query.filter(User.id==assignment.user_id).first().username, 'file': url_for('static', filename='submissions/'+str(assignment.file_location))} for assignment in assignments]) 203 | 204 | 205 | @app.route('/retrieve-channels/', methods=['POST']) 206 | def retrieve_channels(classroom): 207 | channels = Channel.query.filter(classroom==Channel.classroom_id) 208 | return jsonify({ 'result': [ { 'id': channel.id, 'name': channel.name } for channel in channels ] }) 209 | 210 | @app.route('/retrieve-messages/', methods=['POST']) 211 | def retrieve_messages(channel): 212 | POSTS_COUNT_ONLOAD = 50 213 | messages = Message.query.filter(Message.channel_id==channel, Message.ischild==-1) 214 | return jsonify({'result': [ { 'id': message.id, 215 | 'content': message.contents, 216 | 'date': message.date, 217 | 'author': { 218 | 'id': message.author_id, 219 | 'name': User.query.filter(User.id==message.author_id)[0].first_name + ' ' + User.query.filter(User.id==message.author_id)[0].last_name, 220 | 'avatar':url_for('static', filename="imgs/" + User.query.filter(User.id==message.author_id)[0].image_file)} 221 | } for message in messages][-POSTS_COUNT_ONLOAD:] #returning the last ten messages in this channel 222 | }) 223 | 224 | @app.route('/app/activity', methods=['POST']) 225 | def app_activity(): 226 | messages = Message.query.filter(Message.author_id==current_user.get_id()) 227 | return render_template('activity.html', data=messages) 228 | 229 | @app.route('/app/meetings', methods=['POST']) 230 | def app_meetings(): 231 | return render_template('meetings.html') 232 | 233 | @app.route('/app/calls', methods=['POST']) 234 | def app_calls(): 235 | return render_template('calls.html') -------------------------------------------------------------------------------- /classroom_manager/site.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/site.sqlite3 -------------------------------------------------------------------------------- /classroom_manager/static/css/home.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif 5 | } 6 | .bgImage{ 7 | width: 100%; 8 | height: 100vh; 9 | background-size: cover; 10 | background-position: center; 11 | z-index: -1111; 12 | } 13 | 14 | .nav_bar { 15 | width: 100%; 16 | height: 70px; 17 | color: #fff; 18 | border-bottom: 1px solid rgba(255,255,255,0.1); 19 | position: fixed; 20 | z-index: 1111; 21 | } 22 | 23 | .nav_bar h1 { 24 | margin: 0; 25 | padding: 0; 26 | text-transform: uppercase; 27 | float: left; 28 | line-height: 70px; 29 | margin-left: 70px; 30 | } 31 | 32 | nav { 33 | float: right; 34 | } 35 | 36 | nav ul { 37 | margin: 0; 38 | padding: 0; 39 | } 40 | 41 | nav ul li { 42 | list-style: none; 43 | display: inline-block; 44 | } 45 | 46 | nav ul li a { 47 | text-transform: uppercase; 48 | text-decoration: none; 49 | color: #fff; 50 | padding: 0 30px; 51 | line-height: 70px; 52 | display: block; 53 | transition: .4s; 54 | } 55 | 56 | nav ul li a:hover { 57 | color: #e74c3c; 58 | transition: .4s; 59 | } 60 | 61 | .overay { 62 | width: 100%; 63 | height: 100%; 64 | background: rgba(0,0,0,.6); 65 | } 66 | 67 | .text { 68 | position: absolute; 69 | top: 40%; 70 | left: 10%; 71 | color: #fff; 72 | z-index: 22222; 73 | width: 500px; 74 | height: 300px; 75 | background: transparent; 76 | } 77 | 78 | .text:before { 79 | content: ''; 80 | position: absolute; 81 | top: -5px; 82 | left: 7%; 83 | transform: translate(-50%, -50%); 84 | width: 70px; 85 | height: 5px; 86 | background: #e74c3c; 87 | } 88 | 89 | .text h1 { 90 | margin: 0; 91 | padding: 0; 92 | font-weight: 700; 93 | color: #e74c3c; 94 | font-size: 50px; 95 | } 96 | 97 | .text h1 span { 98 | font-weight: normal; 99 | color: #F2F2F2; 100 | } 101 | 102 | .text p { 103 | padding: 20px 50px 20px 0; 104 | line-height: 22px; 105 | color: #F2F2F2; 106 | text-transform: uppercase; 107 | } 108 | 109 | button { 110 | border: none; 111 | background: transparent; 112 | color: #fff; 113 | border: 1px solid #fff; 114 | font-size: 16px; 115 | padding: 10px 30px; 116 | border-radius: 5px; 117 | transition: .3s; 118 | text-transform: uppercase; 119 | } 120 | 121 | .get-now { 122 | background: #e74c3c; 123 | border: 1px solid #e74c3c; 124 | margin-right: 20px; 125 | } 126 | 127 | .get-now:hover { 128 | border: 1px solid #fff; 129 | background: #fff; 130 | color: #000; 131 | transition: .3s; 132 | } 133 | 134 | .book-place:hover { 135 | background: #e74c3c; 136 | border: 1px solid #e74c3c; 137 | transition: .3s; 138 | } -------------------------------------------------------------------------------- /classroom_manager/static/css/styles.css: -------------------------------------------------------------------------------- 1 | .searchbar { 2 | width: 100%; 3 | border-radius: 3px; 4 | height: 1.8em; 5 | } 6 | .selected_item { 7 | background-color: rgb(71,72, 90); 8 | } 9 | .navbar { 10 | background-color: rgb(179, 86, 86); 11 | } 12 | .sidebar { 13 | margin-top: 10px; 14 | width: 7.2em; 15 | color:white; 16 | font-size: 10px; 17 | text-align: center; 18 | background-color: rgb(117, 31, 25); 19 | display:block; 20 | } 21 | #app_container { 22 | margin:1px; 23 | margin-left: 4.8em; 24 | padding-top: 0.6em; 25 | display:block; 26 | } 27 | #sidebartitle { 28 | margin-left: 0.8em; 29 | } 30 | #app_body { 31 | margin-left: 18%; 32 | display:block; 33 | } 34 | .w3-sidebar { 35 | display:block; 36 | margin-right: 10px; 37 | } 38 | 39 | .w3-button { 40 | width:100%; 41 | } 42 | .w3-bar-item { 43 | width: 100%; 44 | } 45 | .channel_display { 46 | margin-left: 4px; 47 | display:block; 48 | } 49 | .messagescontainer { 50 | width: 100%; 51 | border-radius: 5px; 52 | min-height: 460px; 53 | max-height: 80vh; 54 | overflow-y: scroll; 55 | position: relative; 56 | display:block; 57 | } 58 | .user_chat_container { 59 | width: 100%; 60 | border-radius: 5px; 61 | min-height: 600px; 62 | max-height: 85vh; 63 | overflow-y: scroll; 64 | position: relative; 65 | display:block; 66 | } 67 | .tab-content { 68 | width: 100%; 69 | } 70 | .channel_display h4 { 71 | min-width: 100px; 72 | width: 700px; 73 | border-radius: 5px; 74 | margin-top: 0.6em; 75 | margin-bottom: 0.3em; 76 | } 77 | .message { 78 | padding: 10px; 79 | margin-bottom: 15px; 80 | background-color: rgba(49, 170, 168, 1); 81 | border-radius: 4px; 82 | border-left: 4px solid grey; 83 | width: 99.99%; 84 | } 85 | .my_second_sidebar { 86 | min-width: 100px; 87 | width: 14%; 88 | } 89 | .textinput { 90 | padding: 4px; 91 | margin: 4px; 92 | border-color: white; 93 | border-bottom: 4px solid rgb(70,71,117); 94 | border-radius: 4px; 95 | width: 99.99%; 96 | color: black; 97 | } 98 | #new_conversation_form { 99 | display: none; 100 | } 101 | .upper_part { 102 | font-size: 18px; 103 | margin-top: 2px; 104 | } 105 | .fas { 106 | color:black; 107 | } 108 | .fas:hover { 109 | color:white; 110 | } 111 | #note_form { 112 | margin-top: 1em; 113 | } 114 | .btn { 115 | z-index: 0; 116 | } 117 | /* The Overlay (background) */ 118 | .overlay { 119 | /* Height & width depends on how you want to reveal the overlay (see JS below) */ 120 | height: 100%; 121 | width: 0; 122 | position: fixed; /* Stay in place */ 123 | z-index: 1; /* Sit on top */ 124 | left: 0; 125 | top: 0; 126 | background-color: rgb(0,0,0); /* Black fallback color */ 127 | background-color: rgba(0,0,0, 0.9); /* Black w/opacity */ 128 | overflow-x: hidden; /* Disable horizontal scroll */ 129 | transition: 0.5s; /* 0.5 second transition effect to slide in or slide down the overlay (height or width, depending on reveal) */ 130 | } 131 | .overlay-content { 132 | position: relative; 133 | top: 10%; 134 | width: 100%; 135 | text-align: center; 136 | margin-top: 30px; 137 | } 138 | .overlay a { 139 | padding: 8px; 140 | text-decoration: none; 141 | font-size: 36px; 142 | color: #818181; 143 | display: block; 144 | transition: 0.3s; 145 | } 146 | .overlay a:hover, .overlay a:focus { 147 | color: #f1f1f1; 148 | } 149 | .overlay .closebtn { 150 | position: absolute; 151 | top: 20px; 152 | right: 45px; 153 | font-size: 60px; 154 | } 155 | .overlay-content p{ 156 | color:white; 157 | font-size: 24px; 158 | } 159 | .action-container { 160 | width:30%; 161 | margin: auto; 162 | } 163 | .action-title { 164 | font-weight: bold; 165 | } 166 | .user-avatar { 167 | border-radius: 4px; 168 | } -------------------------------------------------------------------------------- /classroom_manager/static/imgs/1_vYtDyAbOMTJnArJvXix5wg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/1_vYtDyAbOMTJnArJvXix5wg.jpeg -------------------------------------------------------------------------------- /classroom_manager/static/imgs/7bbcc1707ce09218fa84866a65b3c871.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/7bbcc1707ce09218fa84866a65b3c871.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/800px_COLOURBOX4159486.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/800px_COLOURBOX4159486.jpg -------------------------------------------------------------------------------- /classroom_manager/static/imgs/Periodic_Table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/Periodic_Table.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/call-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/call-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/chat-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/chat-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/default_class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/default_class.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/defaultuser-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/defaultuser-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/dropdown-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/dropdown-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/gear-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/gear-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/group-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/group-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/meetings-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/meetings-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/notification-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/notification-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/plus-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/plus-icon.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/storage-nand_nor_gates-f_mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/storage-nand_nor_gates-f_mobile.png -------------------------------------------------------------------------------- /classroom_manager/static/imgs/trig.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/trig.gif -------------------------------------------------------------------------------- /classroom_manager/static/imgs/website_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/classroom_manager/static/imgs/website_icon.png -------------------------------------------------------------------------------- /classroom_manager/static/js/app.js: -------------------------------------------------------------------------------- 1 | /* Client script */ 2 | document.body.style.zoom = 0.8; 3 | 4 | const app_container = $('#app_container'); 5 | const app_body = app_container.children("#app_body"); 6 | let selected_page = null; 7 | const socket = io.connect('http://' + document.domain + ':' + location.port); 8 | socket.on('connect', function() { 9 | console.log("Connected to the network server."); 10 | }); 11 | 12 | $(document).ready(function() { 13 | $('#mySidebar .w3-bar-item').on('click', function(event) { 14 | let this_element = this; 15 | if($(this).attr('name') != selected_page){ 16 | selected_page = $(this).attr('name'); 17 | $.ajax({url: '/app/' + $(this).attr('name'), type: 'POST'}).done(function (data) { 18 | app_container.html(data); 19 | $(this_element).addClass('selected_item'); 20 | $('#mySidebar > .w3-bar-item').each(function(index){ 21 | if (this !== this_element){ 22 | $(this).removeClass('selected_item'); 23 | } 24 | }); 25 | }); 26 | } 27 | }); 28 | $('#joinclassform').on('submit', function(event) { 29 | event.preventDefault(); 30 | 31 | $.ajax({data: {code: $('#classroom_code').val()}, type:'POST', url:'/join-team'}).done(function(data) { 32 | if (data.error) { 33 | console.log(data.error); 34 | $('#join_notice').css('display', 'block'); 35 | $('#join_notice').html(data.error); 36 | } 37 | else { 38 | $('#menulist').append(``); 39 | socket.emit('join-room', data.result.id); 40 | } 41 | }); 42 | }); 43 | $('#teamcreationform').on('submit', function(event) { 44 | $.ajax({ 45 | data: { 46 | team_name: $('#teamname').val(), 47 | team_description: $('#teamdescription').val() 48 | }, 49 | type: 'POST', 50 | url: '/create-team' 51 | }).done(function(data) { 52 | const notice_field = $('#notice'); 53 | if (data.error) { 54 | notice_field.html(data.error); 55 | notice_field.addClass('alert-danger'); 56 | notice_field.css('display', 'block'); 57 | console.log("error: team was not created due to the following < " + data.error + " >"); 58 | } 59 | else { 60 | notice_field.css('display', 'block'); 61 | notice_field.addClass('alert-success'); 62 | notice_field.html("classroom `" + $('#teamname').val() + "` has been created."); 63 | socket.emit('join-room', data.new_classroom.id); 64 | $('#teamname').val(''); 65 | $('#teamdescription').val(''); 66 | setTimeout(() => { 67 | notice_field.html(''); 68 | notice_field.hide(); 69 | $("#teammodal").hide(); 70 | }, 1624); 71 | if($('#classrooms').hasClass('selected_item')) { 72 | $('#menulist').append(``); 73 | } 74 | } 75 | }); 76 | event.preventDefault(); 77 | }); 78 | }); 79 | 80 | const toggle_class_creator = () => { 81 | $('#teammodal').css('display', 'block'); 82 | $('#teammodal').find('.alert').css('display', 'none'); 83 | } 84 | const toggle_class_join = () => { 85 | $('#joinclass_modal').css('display', 'block'); 86 | $('#join_notice').css('display', 'none'); 87 | } 88 | const close_settings_menu = () => { 89 | $('#classroom_settings').css('width', '0%'); 90 | } -------------------------------------------------------------------------------- /classroom_manager/static/js/chat.js: -------------------------------------------------------------------------------- 1 | selected_user = null; 2 | 3 | const toggle_username_input = () => { 4 | if($('#username_input_id').is(':visible')) { 5 | $('#username_input_id').hide(); 6 | } 7 | else { 8 | $('#username_input_id').show(); 9 | } 10 | } 11 | const add_user_message = (message) => { 12 | $('#user_chat_box').append(`
${message.author} ${message.date}

${message.content}

`); 13 | $('#user_chat_box').scrollTop(10000); 14 | 15 | } 16 | const set_user_chat_title = (title) => { 17 | $('#chat_title').html(title); 18 | } 19 | const clear_chat = () => { 20 | $('#user_chat_box').html(''); 21 | } 22 | $(document).on('submit', '#username_input_id', function(event) { 23 | event.preventDefault(); 24 | $.ajax({url:'/add-contact', type:'POST', data: {'username': $('#username_input').val()}}).done(function(data) { 25 | if(data.error) { 26 | console.log(data.error); 27 | } 28 | else { 29 | $('#menulist').append(``); 30 | } 31 | }); 32 | $('#username_input').val(''); 33 | }); 34 | $(document).on('click', '.user', function() { 35 | id = parseInt($(this).attr('id').slice(1, $(this).attr('id').length), 10); 36 | selected_user = id; 37 | this_element = $(this); 38 | $.ajax({url:`retrieve-directmessages/${id}`, type:'POST'}).done(function(data) { 39 | console.log(data); 40 | clear_chat(); 41 | set_user_chat_title(this_element.html()); 42 | for(let i = 0; i < data.messages.length; i++) { 43 | add_user_message(data.messages[i]); 44 | } 45 | }); 46 | }); 47 | $(document).on('submit', '#message_input', function(e) { 48 | e.preventDefault(); 49 | message = $('#message_input input').val() 50 | if (selected_user && message) { 51 | socket.emit('direct_message', {'to': selected_user, 'message': message}); 52 | } 53 | $(this).children('input').val(''); 54 | }); 55 | socket.on('direct_message', function(data) { 56 | add_user_message(data); 57 | }); -------------------------------------------------------------------------------- /classroom_manager/static/js/classroom.js: -------------------------------------------------------------------------------- 1 | selected_channel = null; 2 | selected_classroom = null; 3 | const clear_chatbox = () => { 4 | const chat_box = $('#channel_display .messagescontainer').first(); 5 | chat_box.html(''); 6 | } 7 | const set_chat_title = (server_title,channel_title) => { 8 | const chat_title = $('#channel_display .upper_part .title').first(); //getting the first h4 element inside of the channel_display element 9 | chat_title.html(`${server_title} ${channel_title}`); 10 | $(chat_title).find('i').remove(); 11 | } 12 | const clear_submissions = () => { 13 | $('#submissions').html(''); 14 | } 15 | const add_message = (message_data) => { 16 | const chat_box = $('#channel_display .messagescontainer').first(); 17 | chat_box.append(`
${message_data.author.name} ${message_data.date}

${message_data.content}

`); 18 | $(chat_box).scrollTop(10000); 19 | } 20 | const clear_notes = () => { 21 | const notes = $('#note-container'); 22 | const notes_list = $ ('#note-list'); 23 | notes.html(''); 24 | notes_list.html(''); 25 | } 26 | const add_note = (note_data, active) => { 27 | const note_list = $('#note-list'); 28 | const note_container = $('#note-container'); 29 | let id = note_data.note_title.replaceAll(' ', '') + note_data.note_id.toString(10); 30 | note_list.append(`${note_data.note_title}`); 31 | additional_text = ''; 32 | if(note_data.note_img) { 33 | additional_text += ``; 34 | } 35 | note_container.append(`
${additional_text}

${note_data.note_text}

`); 36 | } 37 | const add_assignment = (assignment_data, is_super) => { 38 | const assignment_list = $('#assignments-list'); 39 | if (assignment_data.id) { 40 | if(is_super) { 41 | assignment_list.append(`${assignment_data.text}${assignment_data.duedate} `); 42 | } 43 | else { 44 | if (assignment_data.submission_state) { 45 | assignment_list.append(`${assignment_data.text}${assignment_data.duedate}Already submitted `); 46 | } 47 | else { 48 | assignment_list.append(`${assignment_data.text}${assignment_data.duedate}
`); 49 | 50 | } 51 | } 52 | } 53 | } 54 | const clear_assignments = () => { 55 | const assignment_list = $('#assignments-list'); 56 | assignment_list.html(''); 57 | } 58 | const toggle_submissions_menu = () => { 59 | $('#testest').css('display', 'block'); 60 | } 61 | $(document).ready(function() { 62 | $('body').click(function(e) { 63 | var target = $(e.target); 64 | if(!target.is('#new_conversation_form') && !target.is('#new_conversation_form input')) { 65 | is_visible = $('#new_conversation_form').css('display'); 66 | if (is_visible != 'none') { 67 | $('#new_conversation_form').css('display', 'none'); 68 | $('#newconversation').css('display', 'block'); 69 | } 70 | } 71 | }); 72 | $(document).on('submit', '#note_form', function(event) { 73 | let form_data = new FormData($('#note_form')[0]); 74 | form_data.append('channel_id', selected_channel); 75 | event.preventDefault(); 76 | if(selected_channel != null) { 77 | $.ajax({data: form_data,contentType:false, processData:false, url:'add-note', method:'POST'}).done(function(data) { 78 | $("#note_form").trigger("reset"); 79 | add_note(data['new_note'], ''); 80 | }); 81 | } 82 | }); 83 | $(document).on('click','#menulist .classroom',function(){ 84 | let id = parseInt($(this).attr('id').slice(1, $(this).attr('id').length), 10); 85 | selected_classroom = id; 86 | $.ajax({url:`retrieve-channels/${id}`, type:'POST'}).done((data) => { 87 | console.log(data); 88 | if(data['result'].length > 0){ 89 | let result = ''; 90 | for(let i = 0; i < data['result'].length; i++) { 91 | result+= `
  • ${data['result'][i].name}
  • `; 92 | } 93 | $('#menulist ul').html(''); 94 | $(this).parent().children('.channels').first().html(result); 95 | } 96 | }); 97 | }); 98 | $(document).on('click', '.classroom i', function() { 99 | $('#classroom_settings').css('width', '100%'); 100 | let id = parseInt($(this).parent().attr('id').slice(1, $(this).parent().attr('id').length),10); 101 | $.ajax({url:`classroom-settings/${id}`, type:'POST'}).done(function(data) { 102 | console.log(data); 103 | $('#code_id').html(data['room_code']); 104 | $('#channel_selection').html(''); 105 | $('#user_selection').html(''); 106 | for(let i = 0; i < data.channels.length;i++) { 107 | $('#channel_selection').append(``); 108 | } 109 | for(let i = 0; i < data.members.length; i++) { 110 | $('#user_selection').append(``); 111 | } 112 | if(data.permission) { 113 | $('.permission').show(); 114 | } 115 | else { 116 | $('.permission').hide(); 117 | } 118 | }); 119 | }); 120 | $(document).on('click', '#channels_action_submission_button', function() { 121 | channel_id = $('#channel_selection').children("option:selected").val(); 122 | socket.emit('channel_action', {'classroom_id': selected_classroom, 'action': $('#action_selection').children("option:selected").html(), 'channel_id': channel_id, 'name_input': $('#name_input').val()}); 123 | }); 124 | $(document).on('click', '#users_action_submission_button', function() { 125 | user_id = $('#user_selection').children("option:selected").val(); 126 | socket.emit('user_action', {'classroom_id': selected_classroom, 'action': $('#action_selection_users').children("option:selected").html(), 'user_id': user_id}); 127 | }); 128 | $(document).on('change', '#action_selection', function() { 129 | let selectedAction = $(this).children("option:selected").html(); 130 | if(selectedAction == 'rename' || selectedAction == 'add') { 131 | console.log('selected action is rename'); 132 | $('#name_input').css('display', 'block'); 133 | } 134 | else { 135 | $('#name_input').css('display', 'none'); 136 | } 137 | }); 138 | $(document).on('click', '.channels li', function() { 139 | let id = parseInt($(this).attr('id').slice(1, $(this).attr('id').length), 10); //getting channel id 140 | selected_channel = id; 141 | set_chat_title($(this).parent().parent().children('a').first().html(),$(this).html()); 142 | $.ajax({url:`retrieve-messages/${id}`, type:'POST'}).done((data) => { 143 | clear_chatbox() 144 | if (data.result.length) { 145 | for(let i=0; i < data.result.length; i++) { 146 | add_message(data.result[i]); 147 | } 148 | } else { 149 | let message = {'author': {'name': 'SC. Bot'}, 'content': 'No messages to display', 'date': 'just now'}; 150 | add_message(message); 151 | } 152 | }); 153 | $.ajax({url:`retrieve-notes/${id}`, type:'POST'}).done((data) => { 154 | clear_notes(); 155 | if(data.length) { 156 | for(let i = 0;i < data.length; i++) { 157 | if(i == 0) { 158 | add_note(data[i], 'active'); 159 | } 160 | else { 161 | add_note(data[i], ''); 162 | } 163 | } 164 | } 165 | else { 166 | new_note = {'note_title': 'No data', 'note_text': 'No data', 'note_id': -1}; 167 | add_note(new_note,'active'); 168 | } 169 | }); 170 | $.ajax({url:`retrieve-assignments/${id}`, type:'POST'}).done((data) => { 171 | console.log(data); 172 | clear_assignments(); 173 | if(data.assignments.length) { 174 | result = false; 175 | if(data.role == 'super') { 176 | result = true; 177 | } 178 | for(let i = 0; i < data.assignments.length; i++) { 179 | add_assignment(data.assignments[i], result); 180 | } 181 | } 182 | else { 183 | new_assignment = {'text': 'none', 'duedate': 'none', 'submission_state': '.'}; 184 | add_assignment(new_assignment); 185 | } 186 | }); 187 | }); 188 | $(document).on('click', '#newconversation', function() { 189 | $('#new_conversation_form').css('display', 'block'); //showing new conversation form 190 | $('#newconversation').css('display', 'none'); //hiding newconversation button 191 | }); 192 | $(document).on('keydown', '#new_conversation_form', function(event) { 193 | if (event.keyCode == 13) { 194 | event.preventDefault(); //preventing form from submitting 195 | const user_input = $('#new_conversation_input').val(); //assigning the input to a variable 196 | $('#new_conversation_input').val(''); //setting input value to empty string 197 | if (selected_channel != null) { 198 | socket.emit('channel_conversation', {'channel_id': selected_channel, 'message': user_input}); 199 | } 200 | } 201 | }); 202 | $(document).on('click', '#regenerate_code', function() { 203 | socket.emit('code_regeneration_req', {'classroom_id': selected_classroom}); 204 | }); 205 | $(document).on('click', '#leave_classroom', function() { 206 | socket.emit('classroom_leave', {'classroom_id': selected_classroom}); 207 | set_chat_title('', 'No channel to display...'); 208 | clear_notes(); 209 | clear_chatbox(); 210 | close_settings_menu(); 211 | $(`[id='%${selected_classroom}']`).parent().remove(); 212 | }); 213 | $(document).on('submit', "#assignment_form", function(event) { 214 | event.preventDefault(); 215 | $('#selected_channel_input').val(selected_channel); 216 | $.ajax({url:'/add-assignment', type:'POST', data: $('#assignment_form').serialize(), success: function(data) { 217 | add_assignment(data, true); 218 | }}); 219 | }); 220 | $(document).on('submit', '#homework_submission', function(event) { 221 | event.preventDefault(); 222 | let form_data = new FormData($('#homework_submission')[0]); 223 | form_data.append('channel_id', selected_channel); 224 | form_data.append('assignment_id', parseInt($('#homework_submission').closest('tr').attr('value'), 10)); 225 | event.preventDefault(); 226 | if(selected_channel != null) { 227 | $.ajax({data: form_data,contentType:false, processData:false, url:'/homework-submit', method:'POST'}).done(function(data) { 228 | $('#homework_submission').html('Already submitted'); 229 | }); 230 | } 231 | }); 232 | $(document).on('click', '.show_submission_button', function() { 233 | id = parseInt($(this).closest('tr').attr('value'), 10); 234 | console.log(id); 235 | $.ajax({url:`retrieve-submissions/${id}`, type:'POST'}).done(function(data) { 236 | clear_submissions(); 237 | $('#submissions').append('Username Homework File'); 238 | for(i = 0; i < data.length; i++) { 239 | $('#submissions').append(`${data[i].name}Download submission file`); 240 | } 241 | }); 242 | }); 243 | }); 244 | socket.on('channel_conversation', function(data) { 245 | if(data.channel_id == selected_channel) { 246 | console.log(data); 247 | add_message(data); 248 | } 249 | else { 250 | //Code for notification goes here 251 | } 252 | }); 253 | socket.on('channel_delete', function(data) { 254 | if(selected_channel == parseInt(data['id'], 10)) { 255 | set_chat_title('','No channel to display...'); 256 | clear_notes(); 257 | clear_chatbox(); 258 | } 259 | if(selected_classroom == data['classroom_id']) { 260 | $(`[id='%${data['classroom_id']}']`).parent().children('ul').children().each(function() { 261 | if($(this).attr('id') == '$' + String(data['id'])){ 262 | $(this).remove(); 263 | $('#channel_selection').children().each(function() { 264 | if($(this).val() == parseInt(data['id'], 10)) { 265 | $(this).remove(); 266 | } 267 | }); 268 | } 269 | }) 270 | } 271 | }); 272 | socket.on('new_channel', function(data) { 273 | if(selected_classroom == data['classroom_id']) { 274 | $(`[id='%${data['classroom_id']}']`).parent().children('ul').append(`
  • ${data['name']}
  • `); 275 | $('#channel_selection').append(``); 276 | } 277 | }); 278 | socket.on('channel_rename', function(data) { 279 | if(selected_classroom == data['classroom_id']) { 280 | $(`[id='%${data['classroom_id']}']`).parent().children('ul').children().each(function() { 281 | if($(this).attr('id') == '$' + String(data['id'])) { 282 | $(this).html('' + data['new_name']); 283 | } 284 | }); 285 | $('#channel_selection').children().each(function() { 286 | if($(this).val() == parseInt(data['id'], 10)) { 287 | $(this).html(data['new_name']); 288 | } 289 | }); 290 | } 291 | }); 292 | socket.on('code_regeneration', function(data) { 293 | if(selected_classroom == data['classroom_id']) { 294 | $('#code_id').html(data['code']); 295 | } 296 | }); 297 | socket.on('user_kick', function(data) { 298 | console.log(data); 299 | if(parseInt(data['user_id'], 10) == parseInt($('#user_id').attr('value'), 10)) { 300 | $(`[id='%${data['classroom_id']}']`).parent().remove(); 301 | if(selected_classroom == data['classroom_id']) { 302 | set_chat_title('','No channel to display...'); 303 | clear_notes(); 304 | clear_chatbox(); 305 | } 306 | } 307 | }); -------------------------------------------------------------------------------- /classroom_manager/static/submissions/homeworktest.txt: -------------------------------------------------------------------------------- 1 | blah blah blah -------------------------------------------------------------------------------- /classroom_manager/templates/activity.html: -------------------------------------------------------------------------------- 1 |
    2 |

    Your recent messages

    3 |
    4 | {% for message in data %} 5 |
    You on {{message.date}}

    {{message.contents}}

    6 | {% endfor %} 7 |
    8 |
    -------------------------------------------------------------------------------- /classroom_manager/templates/app.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block content %} 3 | 4 | 23 | 24 | 30 | 31 |
    32 |
    33 | 34 |
    35 | 57 |
    58 | 59 |
    60 | 79 |
    80 | 81 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | {% endblock content %} -------------------------------------------------------------------------------- /classroom_manager/templates/app_layout.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    {{title}}

    4 |
    5 |
    6 | 18 |
    19 | 20 |
    21 | {% block app %}{% endblock %} 22 |
    -------------------------------------------------------------------------------- /classroom_manager/templates/calls.html: -------------------------------------------------------------------------------- 1 |
    -------------------------------------------------------------------------------- /classroom_manager/templates/chats.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    {{title}}

    4 |
    5 |
    6 | 24 |
    25 |
    26 |
    27 |
    No user chat to display...
    28 |
    29 |
    30 |
    SC. Bot Just now

    No messages to display.

    31 | 32 |
    33 |
    34 | 35 |
    36 |
    -------------------------------------------------------------------------------- /classroom_manager/templates/classrooms.html: -------------------------------------------------------------------------------- 1 | {% extends "app_layout.html" %} 2 | {% block app %} 3 | 4 |
    5 | 6 |
    7 |

    8 | No channel to display... 9 |

    10 |
    11 |
    12 | 23 |
    24 |
    25 |
    26 |
    SC. Bot just now.

    No messages to display...

    27 |
    28 | 29 |
    30 | 31 |
    32 |
    33 |
    34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
    TextDuesubmission
    45 |
    46 | 47 | 48 | 49 | 50 |
    51 |
    52 | 53 | 54 |
    55 |
    56 |
    57 |
    58 |
    59 |
    60 |
    61 |
    62 | 63 |
    64 |
    65 |
    66 |
    67 | 68 |
    69 |
    70 |
    71 |
    72 |
    73 |
    74 | 75 | 76 | 77 | 78 |
    79 |
    80 |
    81 |
    82 |
    83 |
    84 | × 85 |
    86 |

    Classroom code: ...

    87 |
    88 | 89 | 90 | 91 |
    92 |
    93 |

    Channels:

    94 |
    95 |
    101 | 104 |
    105 | 106 |
    107 | 108 |
    109 |
    110 |

    Users:

    111 |
    112 | 116 |
    117 | 120 |
    121 |
    122 |
    123 |
    124 |
    125 | 126 | {% endblock app %} 127 | -------------------------------------------------------------------------------- /classroom_manager/templates/home.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Smart class - {{title}} 5 | 6 | 7 | 8 | 9 | 10 |
    11 | 25 | 26 |
    27 |
    28 |

    Smart Class

    29 |

    “eLearning doesn't just "happen"! It requires careful planning and implementation.”
    ~~Christopher Palm

    30 |
    31 |
    32 | 33 |
    34 | 35 | 36 | -------------------------------------------------------------------------------- /classroom_manager/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {% if title %} 15 | Smart class - {{title}} 16 | {% else %} 17 | Smart class 18 | {% endif %} 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {% block content %}{% endblock %} 31 | 32 | 33 | -------------------------------------------------------------------------------- /classroom_manager/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block content %} 3 |
    4 |
    5 |
    6 |
    7 |
    8 |
    9 | {{form.hidden_tag()}} 10 |
    11 | {{form.email.label(class="form-control-label")}} 12 | {{form.email(class="form-control form-control-lg")}} 13 | {% if form.email.errors %} 14 | {% for error in form.email.errors %} 15 | {{error}} 16 | {% endfor %} 17 | {% endif %} 18 |
    19 |
    20 | {{form.password.label(class="form-control-label", placeholder='test')}} 21 | {{form.password(class="form-control form-control-lg")}} 22 | {% if form.password.errors %} 23 | {% for error in form.password.errors %} 24 | {{error}} 25 | {% endfor %} 26 | {% endif %} 27 |
    28 |
    29 | {{form.remember.label(class="form-control-label")}} 30 | {{form.remember}} 31 |
    32 |
    33 | {{form.submit(class="btn btn-outline-info")}} 34 |
    35 | 36 | 37 |
    38 |
    39 |
    40 |
    41 |
    42 |
    43 | {% endblock content %} -------------------------------------------------------------------------------- /classroom_manager/templates/meetings.html: -------------------------------------------------------------------------------- 1 |

    Unfortunately this feature isn't implemented yet :(

    -------------------------------------------------------------------------------- /classroom_manager/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% block content %} 3 | 4 |
    5 |
    6 |
    7 |
    8 |
    9 | {{form.hidden_tag()}} 10 |
    11 | {{form.name.label(class="form-control-label")}} 12 | {{form.name(class="form-control form-control-lg")}} 13 |
    14 |
    15 | {{form.last_name.label(class="form-control-label")}} 16 | {{form.last_name(class="form-control form-control-lg")}} 17 |
    18 |
    19 | {{form.username.label(class="form-control-label")}} 20 | {{form.username(class="form-control form-control-lg")}} 21 | {% if form.username.errors %} 22 | {% for error in form.username.errors %} 23 | {{error}} 24 | {% endfor %} 25 | {% endif %} 26 |
    27 |
    28 | {{form.email.label(class="form-control-label")}} 29 | {{form.email(class="form-control form-control-lg")}} 30 | {% if form.email.errors %} 31 | {% for error in form.email.errors %} 32 | {{error}} 33 | {% endfor %} 34 | {% endif %} 35 |
    36 |
    37 | {{form.password.label(class="form-control-label")}} 38 | {{form.password(class="form-control form-control-lg")}} 39 | {% if form.password.errors %} 40 | {% for error in form.password.errors %} 41 | {{error}} 42 | {% endfor %} 43 | {% endif %} 44 |
    45 |
    46 | {{form.confirm_password.label(class="form-control-label")}} 47 | {{form.confirm_password(class="form-control form-control-lg")}} 48 | {% if form.confirm_password.errors %} 49 | {% for error in form.confirm_password.errors %} 50 | {{error}} 51 | {% endfor %} 52 | {% endif %} 53 |
    54 |
    55 | {{form.student_or_teacher.label(class="form-control-label")}} 56 | {{form.student_or_teacher}} 57 |
    58 |
    59 | {{form.submit(class="btn btn-outline-info")}} 60 |
    61 |
    62 |
    63 |
    64 |
    65 |
    66 | 67 | {% endblock content %} -------------------------------------------------------------------------------- /classroom_manager/utils.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | def generate_code(classroom_id): 4 | return str(classroom_id) + '#' + ''.join([ chr(65 + randint(0,25)) for i in range(8)]) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask==1.1.2 2 | flask_sqlalchemy==2.4.4 3 | flask_bcrypt==0.7.1 4 | flask_login==0.5.0 5 | flask_socketio==4.3.1 6 | flask_wtf==0.14.3 7 | email_validator==1.1.2 8 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | from classroom_manager import socketio,app 2 | 3 | if __name__ == "__main__": 4 | socketio.run(app, debug=True,host='0.0.0.0', port=5000) -------------------------------------------------------------------------------- /screenshots/screenshot1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/screenshots/screenshot1.PNG -------------------------------------------------------------------------------- /screenshots/screenshot2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/screenshots/screenshot2.PNG -------------------------------------------------------------------------------- /screenshots/screenshot3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/screenshots/screenshot3.PNG -------------------------------------------------------------------------------- /screenshots/screenshot4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/screenshots/screenshot4.PNG -------------------------------------------------------------------------------- /screenshots/screenshot5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/screenshots/screenshot5.PNG -------------------------------------------------------------------------------- /screenshots/screenshot6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r1karim/classroom-manager/0b8d851f16195c480eda3c2dec44ad153d628b41/screenshots/screenshot6.PNG --------------------------------------------------------------------------------