muizenval

Observe mouse traps remotely
Log | Files | Refs

commit 9e12d65abfa8868555b15a8b51c7c094ee433116
parent e9b1769d171918c95d16d73ec7059376fcf61c67
Author: Friedel Schön <[email protected]>
Date:   Thu, 21 Apr 2022 10:23:22 +0200

changing to iot-project

Diffstat:
M.gitignore | 5+++--
Dpgmles/forms.py | 85-------------------------------------------------------------------------------
Dpgmles/models.py | 36------------------------------------
Dpgmles/routes.py | 287-------------------------------------------------------------------------------
Dpgmles/server.py | 16----------------
Dpgmles/static/.DS_Store | 0
Dpgmles/templates/admin.html | 28----------------------------
Dpgmles/templates/admin_user.html | 58----------------------------------------------------------
Dpgmles/templates/course.html | 38--------------------------------------
Dpgmles/templates/course_overview.html | 49-------------------------------------------------
Dpgmles/templates/new_course.html | 106-------------------------------------------------------------------------------
Mreadme.md | 65+++--------------------------------------------------------------
Mrun-server.py | 2+-
Mserver/app.py | 18+++++++++++++++++-
Aserver/forms.py | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Aserver/models.py | 32++++++++++++++++++++++++++++++++
Aserver/routes.py | 107+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rpgmles/site.db -> server/site.db | 0
Rpgmles/static/main.css -> server/static/main.css | 0
Rpgmles/static/profile_pics/2e32b4c96a8d8f10.jpg -> server/static/profile_pics/2e32b4c96a8d8f10.jpg | 0
Rpgmles/static/profile_pics/43ed4d3be14daf90.jpg -> server/static/profile_pics/43ed4d3be14daf90.jpg | 0
Rpgmles/static/profile_pics/4895353c1045b870.jpg -> server/static/profile_pics/4895353c1045b870.jpg | 0
Rpgmles/static/profile_pics/4c4083a8362b3c0a.jpg -> server/static/profile_pics/4c4083a8362b3c0a.jpg | 0
Rpgmles/static/profile_pics/53998538edf2342c.png -> server/static/profile_pics/53998538edf2342c.png | 0
Rpgmles/static/profile_pics/6809465dffb2e5ba.jpg -> server/static/profile_pics/6809465dffb2e5ba.jpg | 0
Rpgmles/static/profile_pics/7798432669b8b3ac.jpg -> server/static/profile_pics/7798432669b8b3ac.jpg | 0
Rpgmles/static/profile_pics/85ed1b444539873d.png -> server/static/profile_pics/85ed1b444539873d.png | 0
Rpgmles/static/profile_pics/b6e1c53325f88b74.png -> server/static/profile_pics/b6e1c53325f88b74.png | 0
Rpgmles/static/profile_pics/default.jpg -> server/static/profile_pics/default.jpg | 0
Rpgmles/templates/about.html -> server/templates/about.html | 0
Rpgmles/templates/account.html -> server/templates/account.html | 0
Rpgmles/templates/index.html -> server/templates/index.html | 0
Rpgmles/templates/layout.html -> server/templates/layout.html | 0
Rpgmles/templates/login.html -> server/templates/login.html | 0
Rpgmles/templates/register.html -> server/templates/register.html | 0
35 files changed, 216 insertions(+), 769 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1 +1,2 @@ -__pycache__ -\ No newline at end of file +__pycache__ +.DS_Store +\ No newline at end of file diff --git a/pgmles/forms.py b/pgmles/forms.py @@ -1,85 +0,0 @@ -from flask_login import current_user -from flask_wtf import FlaskForm -from flask_wtf.file import FileAllowed, FileField -from wtforms import BooleanField, HiddenField, PasswordField, SelectField, StringField, SubmitField, TextAreaField -from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError - -from .models import User - -""" registration form for register.html """ -class RegistrationForm(FlaskForm): - username = StringField('Naam', validators=[ DataRequired(), Length(min=2, max=20) ]) - email = StringField('E-Mail', validators=[ DataRequired(), Email() ]) - password = PasswordField('Wachtwoord', validators=[ DataRequired() ]) - confirm_password = PasswordField('Wachtwoord herhalen', validators=[ DataRequired(), EqualTo('password') ]) - submit = SubmitField('Registeren') - - """ validates whether username is already in use """ - def validate_username(self, username): - if User.query.filter_by(username=username.data).first(): - raise ValidationError('Deze gebruikersnaam bestaat al, kies een andere.') - - """ validates whether e-mail is already in use """ - def validate_email(self, email): - if User.query.filter_by(email=email.data).first(): - raise ValidationError('Deze e-mail bestaat al, log in als dat uw e-mail is') - - -""" login form for login.html """ -class LoginForm(FlaskForm): - email = StringField('E-Mail', validators=[ DataRequired(), Email() ]) - password = PasswordField('Wachtwoord', validators=[ DataRequired() ]) - remember = BooleanField('Herinneren') - submit = SubmitField('Inloggen') - - -""" update account form for account.html """ -class UpdateAccountForm(FlaskForm): - username = StringField('Naam', validators=[ DataRequired(), Length(min=2, max=20) ]) - email = StringField('E-Mail', validators=[ DataRequired(), Email() ]) - password = PasswordField('Wachtwoord', validators=[]) - confirm_password = PasswordField('Wachtwoord herhalen', validators=[ EqualTo('password') ]) - picture = FileField('Profielfoto bewerken', validators=[ FileAllowed(['jpg', 'png']) ]) - submit = SubmitField('Bewerken') - - """ validates whether username is already in use """ - def validate_username(self, username): - if username.data != current_user.username and User.query.filter_by(username=username.data).first(): - raise ValidationError('Deze gebruikersnaam bestaat al, kies een andere.') - - """ validates whether e-mail is already in use """ - def validate_email(self, email): - if email.data != current_user.email and User.query.filter_by(email=email.data).first(): - raise ValidationError('Deze e-mail bestaat al, log in als dat uw e-mail is') - -""" update/new course form for new_course.html """ -class NewCourseForm(FlaskForm): - name = StringField('Naam', validators=[ DataRequired(), Length(min=1, max=100) ]) - description = TextAreaField('Beschrijving', validators=[ DataRequired() ]) - teacher_id = SelectField('Docent', validators=[ DataRequired() ], coerce=int) - weekday = SelectField('Weekdag', choices=list(enumerate([ 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag' ]))) - start = StringField('Begin', validators=[ DataRequired() ]) - end = StringField('Einde', validators=[ DataRequired() ]) - location = StringField('Locatie', validators=[ DataRequired(), Length(min=1, max=100) ]) - submit = SubmitField('Versturen') - -""" subscribe form for course.html """ -class SubscribeForm(FlaskForm): - lang_id = HiddenField() - submit = SubmitField('Inschrijven') - - -""" unsubscribe form for course.html """ -class UnsubscribeForm(FlaskForm): - lang_id = HiddenField() - submit = SubmitField('Uitschrijven') - -""" search form for admin.html """ -class SearchForm(FlaskForm): - username = StringField('Naam', validators=[ DataRequired(), Length(min=2, max=20)]) - submit = SubmitField('Zoeken') - -""" account-settings form for admin_user.html """ -class AdminForm(FlaskForm): - type = SelectField('Type', choices=[('client', 'Klant'), ('teacher', 'Docent'), ('admin', 'Administrator')]) - submit = SubmitField('Bewerken') diff --git a/pgmles/models.py b/pgmles/models.py @@ -1,36 +0,0 @@ -from datetime import datetime - -from flask_login import UserMixin - -from .server import db, login_manager - -""" function to load a user from database """ -@login_manager.user_loader -def load_user(user_id): - return User.query.get(int(user_id)) - -""" user-struct for 'user'-database """ -class User(db.Model, UserMixin): - id = db.Column(db.Integer, primary_key=True) - type = db.Column(db.String(6), nullable=False, default="client") - username = db.Column(db.String(20), unique=True, nullable=False) - email = db.Column(db.String(120), unique=True, nullable=False) - image_file = db.Column(db.String(20), nullable=False, default='default.jpg') - password = db.Column(db.String(60), nullable=False) - -""" course-struct for 'course'-database """ -class Course(db.Model): - id = db.Column(db.Integer, primary_key=True) - name = db.Column(db.String(100), nullable=False) - description = db.Column(db.Text, nullable=False) - teacher_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - weekday = db.Column(db.Integer, nullable=False) - start = db.Column(db.String(10), nullable=False, default=datetime.utcnow) - end = db.Column(db.String(10), nullable=False, default=datetime.utcnow) - location = db.Column(db.String(120), nullable=False) - -""" course-member-struct for 'coursemember'-database """ -class CourseMember(db.Model): - id = db.Column(db.Integer, primary_key=True) - course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False) - user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) diff --git a/pgmles/routes.py b/pgmles/routes.py @@ -1,287 +0,0 @@ -import os -import secrets - -from flask import flash, redirect, render_template, request, url_for, abort -from flask_login import current_user, login_required, login_user, logout_user -from PIL import Image -from calendar import Calendar as Month -from datetime import datetime - -from .server import app, bcrypt, db -from .forms import LoginForm, NewCourseForm, AdminForm, RegistrationForm, SearchForm, SubscribeForm, UnsubscribeForm, UpdateAccountForm -from .models import Course, CourseMember, User - - -""" calendar-function to calculate days, etc. for calendar """ -def make_calendar(): - weekdays = list(enumerate(['Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za', 'Zo'])) - - courses = [ None, None, None, None, None, None, None ] - if current_user.is_authenticated: - subscriptions = [ cm.course_id for cm in CourseMember.query.filter_by(user_id=current_user.id) ] - courses = [ ' +\n'.join([ c.name for c in Course.query.filter_by(weekday=i) if c.id in subscriptions ]) for i in range(7) ] - - today = datetime.today() - m = Month() - - rows = [] - for days in m.monthdayscalendar(today.year, today.month): - rows.append([ (i, d, courses[i]) for i, d in enumerate(days) ]) - - return { 'weekdays': weekdays, 'rows': rows } - -""" index.html (home-page) route """ [email protected]("/") -def index(): - courses = Course.query.all() - subscriptions = [] - teachers = User.query.filter_by(type='teacher') - if current_user.is_authenticated: - subscriptions = [ cm.course_id for cm in CourseMember.query.filter_by(user_id=current_user.id) ] - return render_template('index.html', calendar=make_calendar(), courses=courses, subs=subscriptions, teachers=teachers) - -""" about.html route """ [email protected]("/about") -def about(): - return render_template('about.html', calendar=make_calendar(), title='Over ons') - -""" register.html route """ [email protected]("/register", methods=['GET', 'POST']) -def register(): - if current_user.is_authenticated: - flash('U bent al ingelogd', 'warning') - return redirect('/') - form = RegistrationForm() - if form.validate_on_submit(): - hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8') - user = User(username=form.username.data, email=form.email.data, password=hashed_password) - db.session.add(user) - db.session.commit() - flash('Uw profiel werd toegevoegd! U kan nu inloggen.', 'success') - return redirect(url_for('login')) - return render_template('register.html', calendar=make_calendar(), title='Registeren', form=form) - -""" login.html route """ [email protected]("/login", methods=['GET', 'POST']) -def login(): - if current_user.is_authenticated: - flash('U bent al ingelogd', 'warning') - return redirect('/') - form = LoginForm() - if form.validate_on_submit(): - user = User.query.filter_by(email=form.email.data).first() - if user and bcrypt.check_password_hash(user.password, form.password.data): - login_user(user, remember=form.remember.data) - if bcrypt.check_password_hash(user.password, form.email.data): - flash('Wij zullen aanbevelen uw wachtwoord weer te veranderen', 'warning') - next_page = request.args.get('next') - return redirect(next_page if next_page else '/') - else: - flash('Kon niet inloggen, is uw e-mail en wachtwoord juist?', 'danger') - return render_template('login.html', calendar=make_calendar(), title='Inloggen', form=form) - -""" logout route """ [email protected]("/logout") -def logout(): - logout_user() - return redirect('/') - -""" save-picture function for account.html """ -def save_picture(form_picture): - random_hex = secrets.token_hex(8) - _, f_ext = os.path.splitext(form_picture.filename) - picture_fn = random_hex + f_ext - picturepath = os.path.join(app.root_path, 'static/profile_pics', picture_fn) - - output_size = (125, 125) - i = Image.open(form_picture) - i.thumbnail(output_size) - i.save(picturepath) - - return picture_fn - -""" account.html route """ [email protected]("/user/self", methods=[ 'GET', 'POST' ]) -@login_required -def account(): - form = UpdateAccountForm() - if form.validate_on_submit(): - current_user.username = form.username.data - current_user.email = form.email.data - if form.picture.data: - picture_file = save_picture(form.picture.data) - current_user.image_file = picture_file - if form.password.data: - current_user.password = bcrypt.generate_password_hash(form.password.data).decode('utf-8') - db.session.commit() - flash('Uw profiel is bewerkt!', 'success') - return redirect(url_for('account')) - elif request.method == 'GET': - form.username.data = current_user.username - form.email.data = current_user.email - image_file = url_for('static', filename='profile_pics/' + current_user.image_file) - return render_template('account.html', calendar=make_calendar(), title='Profiel', image_file=image_file, form=form) - -""" course.html (course-info) route """ [email protected]("/course/<int:course_id>", methods=[ 'GET', 'POST' ]) -def course(course_id): - sub_form = SubscribeForm() - unsub_form = UnsubscribeForm() - teachers = User.query.filter_by(type='teacher') - subscribed = None - if current_user.is_authenticated: - subscribed = CourseMember.query.filter_by(user_id=current_user.id, course_id=course_id).first() - - if sub_form.validate_on_submit() and not subscribed: - course = CourseMember(user_id=current_user.id, course_id=course_id) - db.session.add(course) - db.session.commit() - flash('U bent nu ingeschreven!', 'success') - return redirect('/') - - if unsub_form.validate_on_submit() and subscribed: - db.session.delete(subscribed) - db.session.commit() - flash('U bent nu uitgeschreven!', 'success') - return redirect('/') - - course = Course.query.get_or_404(course_id) - return render_template('course.html', calendar=make_calendar(), title=course.name, course=course, sub_form=sub_form, unsub_form=unsub_form, subscribed=subscribed is not None, teachers=teachers) - -""" course_overview.html route """ [email protected]("/courses") -@login_required -def course_overview(): - if current_user.type not in [ "admin", "teacher" ]: - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - courses = [ (c, User.query.filter_by(id=c.teacher_id).first() ) for c in Course.query.all() ] - return render_template('course_overview.html', calendar=make_calendar(), legend='Lesoverzicht', courses=courses) - -""" new_course.html route """ [email protected]("/course/new", methods=['GET', 'POST']) -@login_required -def new_course(): - if current_user.type not in [ "admin", "teacher" ]: - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - form = NewCourseForm() - form.teacher_id.choices = [ (g.id, g.username) for g in User.query.filter_by(type='teacher') ] - if form.validate_on_submit(): - course = Course(name=form.name.data, description=form.description.data, teacher_id=form.teacher_id.data, weekday=form.weekday.data, start=form.start.data, end=form.end.data, location=form.location.data) - db.session.add(course) - db.session.commit() - flash('De les is toegevoegd!', 'success') - return redirect(url_for('course_overview')) - return render_template('new_course.html', calendar=make_calendar(), legend='Nieuwe les aanmaken', form=form) - -""" new_course.html (update course) route """ [email protected]("/course/<int:course_id>/update", methods=['GET', 'POST']) -@login_required -def update_course(course_id): - if current_user.type not in [ "admin", "teacher" ]: - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - form = NewCourseForm() - form.teacher_id.choices = [ (g.id, g.username) for g in User.query.filter_by(type='teacher') ] - course = Course.query.get_or_404(course_id) - if form.validate_on_submit(): - course.name = form.name.data - course.description = form.description.data - course.teacher_id = form.teacher_id.data - course.weekday = form.weekday.data - course.start = form.start.data - course.end = form.end.data - course.location = form.location.data - db.session.commit() - flash('De les is bewerkt!', 'success') - return redirect(url_for('course_overview')) - elif request.method == 'GET': - form.name.data = course.name - form.description.data = course.description - form.teacher_id.data = course.teacher_id - form.weekday.data = course.weekday - form.start.data = course.start - form.end.data = course.end - form.location.data = course.location - return render_template('new_course.html', calendar=make_calendar(), form=form, legend='Les aanpassen') - -""" delete-course route """ [email protected]("/course/<int:course_id>/delete", methods=['GET','POST']) -@login_required -def delete_course(course_id): - if current_user.type not in [ "admin", "teacher" ]: - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - course = Course.query.get_or_404(course_id) - db.session.delete(course) - db.session.commit() - return redirect(url_for('course_overview')) - -""" admin.html route """ [email protected]("/users", methods=['GET','POST']) -@login_required -def admin(): - if current_user.type != "admin": - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - form = SearchForm() - if form.validate_on_submit(): - user = User.query.filter_by(username=form.username.data).first() - if user == None: - flash(f'Geen gebrukers gevonden met de gebruikersnaam: {form.username.data}!', 'danger') - else: - flash(f'Gebruiker gevonden met gebruikersnaam: {form.username.data}!', 'success') - return redirect(url_for('admin_user', user_id= user.id)) - return render_template('admin.html', calendar=make_calendar(), form=form) - -""" account-admin route """ [email protected]("/user/<int:user_id>", methods=['GET','POST']) -@login_required -def admin_user(user_id): - if current_user.type != "admin": - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - form = AdminForm() - user = User.query.filter_by(id=user_id).first() - image_file = url_for('static', filename='profile_pics/' + user.image_file) - if form.validate_on_submit(): - user.type = form.type.data - db.session.commit() - flash(f'De gebruiker {user.username} is nu een {user.type}', 'success') - return redirect(url_for('admin')) - elif request.method == 'GET': - form.type.data = user.type - return render_template('admin_user.html', calendar=make_calendar(), form=form, user=user, image_file=image_file) - -""" delete-user route """ [email protected]("/user/<int:user_id>/delete", methods=['GET','POST']) -@login_required -def delete_user(user_id): - if current_user.type != "admin": - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - user = User.query.get_or_404(user_id) - db.session.delete(user) - db.session.commit() - flash(f'De gebruiker {user.username} werd verwijdert', 'success') - return redirect(url_for('admin')) - -""" reset user's password route """ [email protected]("/user/<int:user_id>/reset", methods=['GET','POST']) -@login_required -def reset_user(user_id): - if current_user.type != "admin": - flash('U mag deze website niet bereiken', 'error') - return redirect('/') - user = User.query.get_or_404(user_id) - user.password = bcrypt.generate_password_hash(user.email).decode('utf-8') - db.session.commit() - flash(f'{user.username}\'s is nu zijn/haar e-mail', 'success') - return redirect(url_for('admin')) - -""" 404 not found handler """ [email protected](404) -def not_found(error): - flash(f"Deze pagina werd niet gevonden", 'danger') - return index() # geen redirect om de '/bla' te houden diff --git a/pgmles/server.py b/pgmles/server.py @@ -1,16 +0,0 @@ -from flask import Flask -from flask_bcrypt import Bcrypt -from flask_login import LoginManager -from flask_sqlalchemy import SQLAlchemy - -app = Flask(__name__) -app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245' -app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' -db = SQLAlchemy(app) -bcrypt = Bcrypt(app) -login_manager = LoginManager(app) -login_manager.login_view = 'login' -login_manager.login_message_category = 'info' - -# to run 'routes.py' and make the routes available -from .routes import * diff --git a/pgmles/static/.DS_Store b/pgmles/static/.DS_Store Binary files differ. diff --git a/pgmles/templates/admin.html b/pgmles/templates/admin.html @@ -1,27 +0,0 @@ -{% extends "layout.html" %} -{% block content %} -<h1>Rechten bewerken!</h1> -<div class="content-section"> - <form method="POST" action=""> - {{ form.hidden_tag() }} - <fieldset class="form-group"> - <legend class="border-bottom mb-4">Zoeken</legend> - <div class="form-group"> - {{ form.username.label(class="form-control-label") }} - {% if form.username.errors %} - {{ form.username(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.username.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.username(class="form-control form-control-lg") }} - {% endif %} - </div> - </fieldset> - <div class="form-group"> - {{ form.submit(class="btn btn-outline-info") }} - </div> -</div> -{% endblock content %} -\ No newline at end of file diff --git a/pgmles/templates/admin_user.html b/pgmles/templates/admin_user.html @@ -1,57 +0,0 @@ -{% extends "layout.html" %} -{% block content %} -<div class="media"> - <img class="rounded-circle account-img" src="{{ image_file }}"> - <div class="media-body"> - <h2 class="account-heading">{{ user.username }}</h2> - <p class="text-secondary">{{ user.email }}</p> - </div> -</div> -<form method="POST" action=""> - {{ form.hidden_tag() }} - <fieldset class="form-group"> - <legend class="border-bottom mb-4">Rechten bewerken!</legend> - <div class="form-group"> - {{ form.type.label(class="form-control-label") }} - {% if form.type.errors %} - {{ form.type(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.type.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.type(class="form-control form-control-lg") }} - {% endif %} - </div> - </fieldset> - <div class="form-group"> - <button type="button" class="btn btn-danger btn-sm m-1" data-toggle="modal" - data-target="#deleteModal">Verwijderen</button> - <a class="btn btn-danger btn-sm m-1" href="{{ url_for('reset_user', user_id=user.id) }}">Wachtwoord - terugzetten</a> - <br><br> - {{ form.submit(class="btn btn-outline-info") }} - </div> -</form> -<!-- Modal --> -<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" - aria-hidden="true"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="deleteModalLabel">Profiel verwijderen?</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">&times;</span> - </button> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal">Sluiten</button> - <form action="{{ url_for('delete_user', user_id=user.id) }}" method="POST"> - <input class="btn btn-danger" type="submit" value="Delete"> - </form> - </div> - </div> - </div> -</div> -{% endblock content %} -\ No newline at end of file diff --git a/pgmles/templates/course.html b/pgmles/templates/course.html @@ -1,37 +0,0 @@ -{% extends "layout.html" %} -{% block content %} -<article class="media content-section"> - <div class="media-body"> - <h2><a class="article-title" href="{{url_for('course', course_id=course.id)}}"> - {{ course.name }} - {{ '(subscribed)' if course.id in subs }} - </a></h2> - <p><i>{{ course.description }}</i></p> - <p>wordt gegeven door {% for teacher in teachers if teacher.id == course.teacher_id %}{{ teacher.username }},{% - endfor %}</p> - <p>elke {{ ['maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag', 'zondag'][course.weekday] }} - {{ course.start }} uur t/m {{ course.end }} uur op locatie: <b>{{ course.location }}</b></p> - </div> - <div class="article-metadata"> - {% if current_user.is_authenticated %} - {% if not subscribed %} - <form method="POST" action=""> - {{ sub_form.hidden_tag() }} - <div class="form-group"> - {{ sub_form.submit(class="btn btn-outline-info") }} - </div> - </form> - {% else %} - <form method="POST" action=""> - {{ unsub_form.hidden_tag() }} - <div class="form-group"> - {{ unsub_form.submit(class="btn btn-outline-info") }} - </div> - </form> - {%endif%} - {% else %} - <p><a href="{{ url_for('login') }}">Inloggen om in te schrijven</a></p> - {% endif %} - </div> -</article> -{% endblock content %} -\ No newline at end of file diff --git a/pgmles/templates/course_overview.html b/pgmles/templates/course_overview.html @@ -1,48 +0,0 @@ -{% extends "layout.html" %} -{% block content %} -<article class="media content-section"> - <legend class="border-bottom mb-4"> - <h1>{{ legend }}</h1> - </legend> -</article> -<article class="media content-section"> - <div class="media-body"> - <div> - <a class="btn btn-secondary btn-sm mt-1 mb-1" href="{{ url_for('new_course') }}">Nieuwe les</a> - </div> - </div> -</article> -{% for course, teacher in courses %} -<article class="media content-section"> - <div class="media-body"> - <h2><a class="article-title" href="{{url_for('course', course_id=course.id)}}">{{course.name}} <small>by {{ - teacher.username }}</small></a></h2> - <div> - <a class="btn btn-secondary btn-sm mt-1 mb-1" - href="{{ url_for('update_course', course_id = course.id) }}">Bewerken</a> - <button type="button" class="btn btn-danger btn-sm m-1" data-toggle="modal" - data-target="#deleteModal">Verwijderen</button> - </div> - </div> -</article> -<div class="modal fade" id="deleteModal" tabindex="-1" role="dialog" aria-labelledby="deleteModalLabel" - aria-hidden="true"> - <div class="modal-dialog" role="document"> - <div class="modal-content"> - <div class="modal-header"> - <h5 class="modal-title" id="deleteModalLabel">Les verwijderen?</h5> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">&times;</span> - </button> - </div> - <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal">Sluiten</button> - <form action="{{ url_for('delete_course', course_id=course.id) }}" method="POST"> - <input class="btn btn-danger" type="submit" value="Delete"> - </form> - </div> - </div> - </div> -</div> -{% endfor %} -{% endblock content %} -\ No newline at end of file diff --git a/pgmles/templates/new_course.html b/pgmles/templates/new_course.html @@ -1,105 +0,0 @@ -{% extends "layout.html" %} -{% block content %} -<div class="content-section"> - <form method="POST" action=""> - {{ form.hidden_tag() }} - <fieldset class="form-group"> - <legend class="border-bottom mb-4"><h1>{{ legend }}</h1></legend> - <div class="form-group"> - {{ form.name.label(class="form-control-label") }} - {% if form.name.errors %} - {{ form.name(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.name.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.name(class="form-control form-control-lg") }} - {% endif %} - </div> - <div class="form-group"> - {{ form.description.label(class="form-control-label") }} - {% if form.description.errors %} - {{ form.description(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.description.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.description(class="form-control form-control-lg") }} - {% endif %} - </div> - <div class="form-group"> - {{ form.teacher_id.label(class="form-control-label") }} - {% if form.teacher_id.errors %} - {{ form.teacher_id(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.teacher_id.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.teacher_id(class="form-control form-control-lg") }} - {% endif %} - </div> - <div class="form-group"> - {{ form.weekday.label(class="form-control-label") }} - {% if form.weekday.errors %} - {{ form.weekday(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.weekday.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.weekday(class="form-control form-control-lg") }} - {% endif %} - </div> - <div class="form-group"> - {{ form.start.label(class="form-control-label") }} - {% if form.start.errors %} - {{ form.start(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.start.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.start(class="form-control form-control-lg") }} - {% endif %} - </div> - <div class="form-group"> - {{ form.end.label(class="form-control-label") }} - {% if form.end.errors %} - {{ form.end(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.end.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.end(class="form-control form-control-lg") }} - {% endif %} - </div> - <div class="form-group"> - {{ form.location.label(class="form-control-label") }} - {% if form.location.errors %} - {{ form.location(class="form-control form-control-lg is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.location.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.location(class="form-control form-control-lg") }} - {% endif %} - </div> - </fieldset> - <div class="form-group"> - {{ form.submit(class="btn btn-outline-info") }} - </div> - </form> -</div> -{% endblock content %} -\ No newline at end of file diff --git a/readme.md b/readme.md @@ -1,16 +1,12 @@ -# PROGRAMMEERLES VOOR OUDEREN - -[Repository](https://github.com/MoiBaguette/Webtechnologie-Project) +# REPOSITORY VOOR ONS IOT-PROJECT (5GRONINGEN) ## De server runnen Dit is een dev-server, dus run je met `debug=True`-flag! -*Als onze website zo goed is, om het in production te runnen, verwijder het `debug=True` :beers:* - **Deze repository clonen:** ``` -$ git clone https://github.com/MoiBaguette/Webtechnologie-Project +$ git clone https://github.com/friedelschoen/iot-project ``` **Alle afhankelijkheden installeren:** @@ -20,60 +16,5 @@ $ pip3 install flask wtforms flask_sqlalchemy flask-wtf email_validator flask-bc **De server runnen:** ``` -$ python run.py +$ python run-server.py ``` - - -## Uitleg - -### Bestanden - -| bestand | route | beschrikbaar als<sup>1</sup> | beschrijving | -|-----------------------|-----------------------------|------------------------------|--------------------------------------------------------| -| index.html | / | gast | home-pagina | -| about.html | /about | gast | over ons | -| register.html | /register | gast | registeren van een gebruiker<sup>2</sup> | -| login.html | /login | gast | inloggen van een gebruiker<sup>2,3</sup> | -| | /logout | klant | uitloggen van een gebruiker | -| course_overview.html | /courses | docent | lessen bewerken/verwijderen | -| new_course.html | /course/new | docent | nieuwe les aanmaken | -| course.html | /course/`:course_id` | klant | les informatie | -| new_course.html | /course/`:course_id`/update | docent | les instellingen | -| | /course/`:course_id`/delete | docent | les verwijderen | -| admin.html | /users | admin | gebruiker overzicht<sup>4</sup> | -| account.html | /user/self | klant | profiel instellingen | -| admin_user.html | /user/`:user_id` | admin | gebruiker instellingen | -| | /user/`:user_id`/delete | admin | gebruiker verwijderen | -| | /user/`:user_id`/reset | admin | gebruikers wachtwoord terugzetten<sup>5</sup> | -| index.html | *not found* | | 404 page not found handler | -| **overige bestanden** | | | | -| forms.py | | | alle forms voor de websites | -| models.py | | | alle database structs, om alle tabellen te beschrijven | -| routes.py | | | alle routen en endpoints | -| server.py | | | de server initialatie, database etc. | -| site.db | | | hoofd-database voor users, courses etc. | -| .gitignore | | | om git te stoppen, \__pycache__ mee up te laden | -| run.py | | | om de server te runnen | -| layout.html | | | de basis layout voor alle routen | -| static/main.css | | | de basis stylesheet voor alle routen | -| static/profile_pics | | | map met alle profielfoto's | - -> <sup>1</sup> de hierachie is: gast (niet ingelogd), klant, docent, admin<br> -> dus kan een gast het minste bereiken, een klant ook kan alles bereiken wat gast mag etc. - -> <sup>2</sup> als hij al ingelogd is, wordt weer naar `/` redirect - -> <sup>3</sup> jij kan een `?next=` parameters geven, dan wordt na het inloggen daarheen redirect - -> <sup>4</sup> bij gebruiker zoeken moet de naam overeinkomen met de gebruikers naam, nog geen echte zoek-functie - -> <sup>5</sup> betekent: zijn wachtwoord is dan gelijk aan zijn e-mail om in te loggen en zijn wachtwoord weer te veranderen, als iemand zijn wachtwoord is vergeten - - -## Test Profielen - -| type | e-mail | password | -|---------|-------------------|----------| -| klant | [email protected] | `hallo` | -| docent | [email protected] | `hallo` | -| admin | [email protected] | `hallo` | diff --git a/run-server.py b/run-server.py @@ -1,4 +1,4 @@ -from pgmles.server import app +from server.app import app if __name__ == '__main__': app.run(debug=True) diff --git a/server/app.py b/server/app.py @@ -1 +1,17 @@ -import flask +from flask import Flask +from flask_bcrypt import Bcrypt +from flask_login import LoginManager +from flask_sqlalchemy import SQLAlchemy + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'iot_project' +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' +db = SQLAlchemy(app) +bcrypt = Bcrypt(app) +login_manager = LoginManager(app) +login_manager.login_view = 'login' +login_manager.login_message_category = 'info' + +# to run 'routes.py' and make the routes available +from .routes import * +from .models import * diff --git a/server/forms.py b/server/forms.py @@ -0,0 +1,53 @@ +from flask_login import current_user +from flask_wtf import FlaskForm +from flask_wtf.file import FileAllowed, FileField +from wtforms import BooleanField, HiddenField, PasswordField, SelectField, StringField, SubmitField, TextAreaField +from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError + +from .models import User + +""" registration form for register.html """ +class RegistrationForm(FlaskForm): + username = StringField('Naam', validators=[ DataRequired(), Length(min=2, max=20) ]) + email = StringField('E-Mail', validators=[ DataRequired(), Email() ]) + password = PasswordField('Wachtwoord', validators=[ DataRequired() ]) + confirm_password = PasswordField('Wachtwoord herhalen', validators=[ DataRequired(), EqualTo('password') ]) + submit = SubmitField('Registeren') + + """ validates whether username is already in use """ + def validate_username(self, username): + if User.query.filter_by(name=username.data).first(): + raise ValidationError('Deze gebruikersnaam bestaat al, kies een andere.') + + """ validates whether e-mail is already in use """ + def validate_email(self, email): + if User.query.filter_by(email=email.data).first(): + raise ValidationError('Deze e-mail bestaat al, log in als dat uw e-mail is') + + +""" login form for login.html """ +class LoginForm(FlaskForm): + email = StringField('E-Mail', validators=[ DataRequired(), Email() ]) + password = PasswordField('Wachtwoord', validators=[ DataRequired() ]) + remember = BooleanField('Herinneren') + submit = SubmitField('Inloggen') + + +""" update account form for account.html """ +class UpdateAccountForm(FlaskForm): + username = StringField('Naam', validators=[ DataRequired(), Length(min=2, max=20) ]) + email = StringField('E-Mail', validators=[ DataRequired(), Email() ]) + password = PasswordField('Wachtwoord', validators=[]) + confirm_password = PasswordField('Wachtwoord herhalen', validators=[ EqualTo('password') ]) + picture = FileField('Profielfoto bewerken', validators=[ FileAllowed(['jpg', 'png']) ]) + submit = SubmitField('Bewerken') + + """ validates whether username is already in use """ + def validate_username(self, username): + if username.data != current_user.name and User.query.filter_by(username=username.data).first(): + raise ValidationError('Deze gebruikersnaam bestaat al, kies een andere.') + + """ validates whether e-mail is already in use """ + def validate_email(self, email): + if email.data != current_user.email and User.query.filter_by(email=email.data).first(): + raise ValidationError('Deze e-mail bestaat al, log in als dat uw e-mail is') diff --git a/server/models.py b/server/models.py @@ -0,0 +1,32 @@ +from datetime import datetime + +from flask_login import UserMixin + +from .app import db, login_manager + +""" function to load a user from database """ +@login_manager.user_loader +def load_user(user_id): + return User.query.get(int(user_id)) + + +class User(db.Model, UserMixin): + id = db.Column(db.Integer, primary_key=True) + type = db.Column(db.String(7), nullable=False, default="client") # client, catcher, admin + email = db.Column(db.String(120), unique=True, nullable=False) + name = db.Column(db.String(20), unique=True, nullable=False) + password = db.Column(db.String(60), nullable=False) + image_file = db.Column(db.String(20), nullable=False, default='default.jpg') + +class Home(db.Model): + id = db.Column(db.Integer, primary_key=True) + owner = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + catcher = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + adress = db.Column(db.Text, nullable=False) + +class Trap(db.Model): + id = db.Column(db.Integer, primary_key=True) + mac = db.Column(db.String(16), unique=True, nullable=False) + home = db.Column(db.Integer, db.ForeignKey('home.id'), nullable=False) + last_heartbeat = db.Column(db.Integer, nullable=True, default=0) + caught = db.Column(db.Boolean, nullable=False, default=False) diff --git a/server/routes.py b/server/routes.py @@ -0,0 +1,107 @@ +import os +import secrets + +from flask import flash, redirect, render_template, request, url_for, abort +from flask_login import current_user, login_required, login_user, logout_user +from PIL import Image +from calendar import Calendar as Month +from datetime import datetime + +from .app import app, bcrypt, db +from .forms import LoginForm, NewCourseForm, AdminForm, RegistrationForm, SearchForm, SubscribeForm, UnsubscribeForm, UpdateAccountForm +from .models import User + + +""" index.html (home-page) route """ [email protected]("/") +def index(): + return render_template('index.html') + +""" about.html route """ [email protected]("/about") +def about(): + return render_template('about.html', title='Over ons') + +""" register.html route """ [email protected]("/register", methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + flash('U bent al ingelogd', 'warning') + return redirect('/') + form = RegistrationForm() + if form.validate_on_submit(): + hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8') + user = User(name=form.username.data, email=form.email.data, password=hashed_password) + db.session.add(user) + db.session.commit() + flash('Uw profiel werd toegevoegd! U kan nu inloggen.', 'success') + return redirect(url_for('login')) + return render_template('register.html', title='Registeren', form=form) + +""" login.html route """ [email protected]("/login", methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + flash('U bent al ingelogd', 'warning') + return redirect('/') + form = LoginForm() + if form.validate_on_submit(): + user = User.query.filter_by(email=form.email.data).first() + if user and bcrypt.check_password_hash(user.password, form.password.data): + login_user(user, remember=form.remember.data) + if bcrypt.check_password_hash(user.password, form.email.data): + flash('Wij zullen aanbevelen uw wachtwoord weer te veranderen', 'warning') + next_page = request.args.get('next') + return redirect(next_page if next_page else '/') + else: + flash('Kon niet inloggen, is uw e-mail en wachtwoord juist?', 'danger') + return render_template('login.html', title='Inloggen', form=form) + +""" logout route """ [email protected]("/logout") +def logout(): + logout_user() + return redirect('/') + +""" save-picture function for account.html """ +def save_picture(form_picture): + random_hex = secrets.token_hex(8) + _, f_ext = os.path.splitext(form_picture.filename) + picture_fn = random_hex + f_ext + picturepath = os.path.join(app.root_path, 'static/profile_pics', picture_fn) + + output_size = (125, 125) + i = Image.open(form_picture) + i.thumbnail(output_size) + i.save(picturepath) + + return picture_fn + +""" account.html route """ [email protected]("/user/self", methods=[ 'GET', 'POST' ]) +@login_required +def account(): + form = UpdateAccountForm() + if form.validate_on_submit(): + current_user.name = form.username.data + current_user.email = form.email.data + if form.picture.data: + picture_file = save_picture(form.picture.data) + current_user.image_file = picture_file + if form.password.data: + current_user.password = bcrypt.generate_password_hash(form.password.data).decode('utf-8') + db.session.commit() + flash('Uw profiel is bewerkt!', 'success') + return redirect(url_for('account')) + elif request.method == 'GET': + form.username.data = current_user.name + form.email.data = current_user.email + image_file = url_for('static', filename='profile_pics/' + current_user.image_file) + return render_template('account.html', title='Profiel', image_file=image_file, form=form) + + +""" 404 not found handler """ [email protected](404) +def not_found(error): + flash(f"Deze pagina werd niet gevonden", 'danger') + return index() # geen redirect om de '/bla' te houden diff --git a/pgmles/site.db b/server/site.db Binary files differ. diff --git a/pgmles/static/main.css b/server/static/main.css diff --git a/pgmles/static/profile_pics/2e32b4c96a8d8f10.jpg b/server/static/profile_pics/2e32b4c96a8d8f10.jpg Binary files differ. diff --git a/pgmles/static/profile_pics/43ed4d3be14daf90.jpg b/server/static/profile_pics/43ed4d3be14daf90.jpg Binary files differ. diff --git a/pgmles/static/profile_pics/4895353c1045b870.jpg b/server/static/profile_pics/4895353c1045b870.jpg Binary files differ. diff --git a/pgmles/static/profile_pics/4c4083a8362b3c0a.jpg b/server/static/profile_pics/4c4083a8362b3c0a.jpg Binary files differ. diff --git a/pgmles/static/profile_pics/53998538edf2342c.png b/server/static/profile_pics/53998538edf2342c.png Binary files differ. diff --git a/pgmles/static/profile_pics/6809465dffb2e5ba.jpg b/server/static/profile_pics/6809465dffb2e5ba.jpg Binary files differ. diff --git a/pgmles/static/profile_pics/7798432669b8b3ac.jpg b/server/static/profile_pics/7798432669b8b3ac.jpg Binary files differ. diff --git a/pgmles/static/profile_pics/85ed1b444539873d.png b/server/static/profile_pics/85ed1b444539873d.png Binary files differ. diff --git a/pgmles/static/profile_pics/b6e1c53325f88b74.png b/server/static/profile_pics/b6e1c53325f88b74.png Binary files differ. diff --git a/pgmles/static/profile_pics/default.jpg b/server/static/profile_pics/default.jpg Binary files differ. diff --git a/pgmles/templates/about.html b/server/templates/about.html diff --git a/pgmles/templates/account.html b/server/templates/account.html diff --git a/pgmles/templates/index.html b/server/templates/index.html diff --git a/pgmles/templates/layout.html b/server/templates/layout.html diff --git a/pgmles/templates/login.html b/server/templates/login.html diff --git a/pgmles/templates/register.html b/server/templates/register.html