commit e9b1769d171918c95d16d73ec7059376fcf61c67
Author: Friedel Schön <[email protected]>
Date: Wed, 20 Apr 2022 12:03:41 +0200
first commit
Diffstat:
33 files changed, 1320 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+__pycache__
+\ No newline at end of file
diff --git a/pgmles/forms.py b/pgmles/forms.py
@@ -0,0 +1,85 @@
+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
@@ -0,0 +1,36 @@
+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
@@ -0,0 +1,287 @@
+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
@@ -0,0 +1,16 @@
+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/site.db b/pgmles/site.db
Binary files differ.
diff --git a/pgmles/static/.DS_Store b/pgmles/static/.DS_Store
Binary files differ.
diff --git a/pgmles/static/main.css b/pgmles/static/main.css
@@ -0,0 +1,145 @@
+body {
+ background: #fafafa;
+ color: #333333;
+ margin-top: 5rem;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #444444;
+}
+
+.bg-steel {
+ background-color: #33bc85;
+}
+
+.site-header .navbar-nav .nav-link {
+ color: #eeeeee;
+}
+
+.site-header .navbar-nav .nav-link:hover {
+ color: #ffffff;
+ text-decoration: underline;
+}
+
+.site-header .navbar-nav .nav-link.active {
+ font-weight: 500;
+}
+
+.content-section {
+ background: #ffffff;
+ padding: 10px 20px;
+ border: 1px solid #dddddd;
+ border-radius: 3px;
+ margin-bottom: 20px;
+}
+
+.article-title {
+ color: #444444;
+}
+
+a.article-title:hover {
+ color: #428bca;
+ text-decoration: none;
+}
+
+.article-content {
+ white-space: pre-line;
+}
+
+.article-img {
+ height: 65px;
+ width: 65px;
+ margin-right: 16px;
+}
+
+.article-metadata {
+ padding-bottom: 1px;
+ margin-bottom: 4px;
+ border-bottom: 1px solid #e3e3e3
+}
+
+.article-metadata a:hover {
+ color: #333;
+ text-decoration: none;
+}
+
+.article-svg {
+ width: 25px;
+ height: 25px;
+ vertical-align: middle;
+}
+
+.account-img {
+ height: 125px;
+ width: 125px;
+ margin-right: 20px;
+ margin-bottom: 16px;
+}
+
+.account-heading {
+ font-size: 2.5rem;
+}
+
+.calendar {
+ background-color: #fafafa;
+ width: 100%;
+ color: #818182;
+ border: #dddddd solid 1px;
+}
+
+.calendar .weekend {
+ background-color: #eee;
+}
+
+.calendar th {
+ border-bottom: #ddd solid 1px;
+}
+
+.calendar th, td {
+ /*border: #333 solid 1px;*/
+ text-align: center;
+ width: 100px;
+ position: relative;
+}
+
+.calendar .course {
+ background: rgb(84, 230, 84);
+ border: #444 solid 1px;
+ color: #444
+}
+
+.calendar td .hover-day {
+ visibility: hidden;
+ background-color: #444;
+ color: #eee;
+ text-align: center;
+ border-radius: 6px;
+ padding: 5px 0;
+
+ width: 120px;
+ bottom: 100%;
+ left: 50%;
+ margin-left: -60px; /* Use half of the width (120/2 = 60), to center the tooltip */
+ padding: 5px 0;
+
+
+ /* Position the tooltip */
+ position: absolute;
+ z-index: 1;
+}
+
+.calendar td .hover-day::after {
+ content: "";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: #444 transparent transparent transparent;
+}
+
+
+.calendar td:hover .hover-day {
+ visibility: visible;
+}
+\ No newline at end of file
diff --git a/pgmles/static/profile_pics/2e32b4c96a8d8f10.jpg b/pgmles/static/profile_pics/2e32b4c96a8d8f10.jpg
Binary files differ.
diff --git a/pgmles/static/profile_pics/43ed4d3be14daf90.jpg b/pgmles/static/profile_pics/43ed4d3be14daf90.jpg
Binary files differ.
diff --git a/pgmles/static/profile_pics/4895353c1045b870.jpg b/pgmles/static/profile_pics/4895353c1045b870.jpg
Binary files differ.
diff --git a/pgmles/static/profile_pics/4c4083a8362b3c0a.jpg b/pgmles/static/profile_pics/4c4083a8362b3c0a.jpg
Binary files differ.
diff --git a/pgmles/static/profile_pics/53998538edf2342c.png b/pgmles/static/profile_pics/53998538edf2342c.png
Binary files differ.
diff --git a/pgmles/static/profile_pics/6809465dffb2e5ba.jpg b/pgmles/static/profile_pics/6809465dffb2e5ba.jpg
Binary files differ.
diff --git a/pgmles/static/profile_pics/7798432669b8b3ac.jpg b/pgmles/static/profile_pics/7798432669b8b3ac.jpg
Binary files differ.
diff --git a/pgmles/static/profile_pics/85ed1b444539873d.png b/pgmles/static/profile_pics/85ed1b444539873d.png
Binary files differ.
diff --git a/pgmles/static/profile_pics/b6e1c53325f88b74.png b/pgmles/static/profile_pics/b6e1c53325f88b74.png
Binary files differ.
diff --git a/pgmles/static/profile_pics/default.jpg b/pgmles/static/profile_pics/default.jpg
Binary files differ.
diff --git a/pgmles/templates/about.html b/pgmles/templates/about.html
@@ -0,0 +1,30 @@
+{% extends "layout.html" %}
+{% block content %}
+<article class="media content-section">
+ <div class="media-body">
+ <h2>Over ons</h2>
+ <p>
+ Wij staan voor educatie in Nederland! <i>IT Academy Nederland (ITAN)</i>
+ <!-- Zo heet de opdrachtgever in opdracht schriftelijk vaardigheden -->
+ geeft lessen aan verschillende mensen in heel Nederland, maar tot nu toe nooit aan ouderen.
+ </p>
+ <p>
+ Daarom heeft ITAN in samenwerking met <i>Groningen-Drenthe Systems (GDS)</i> deze pagina ontwikkeld. Zodat
+ ook ouderen zich online in kunnen schrijven voor cursussen; de mogelijkheid hebben
+ in hun vrije tijd iets nieuws te leren.
+ </p>
+ <p>
+ Omdat vele pensioenaten vroeger in BASIC, COBOL of Assembly op hun Commandore 64 programmeerden. Is ons doel
+ om dit
+ op te pakken en te en hier op voort te bouwen met moderne talen zoals Python, Java of JavaScript. Natuurlijk
+ bieden wij ook
+ verschillende cursussen voor beginners aan.
+ </p>
+ <p>
+ U bent hartelijk welkom en wij hopen u in ons lessen te kunnen zien!<br>
+ <a href="{{ url_for('register') }}">Klik hier</a>, om een profiel aan te maken en voor lessen in te
+ schrijven.
+ </p>
+ </div>
+</article>
+{% endblock content %}
+\ No newline at end of file
diff --git a/pgmles/templates/account.html b/pgmles/templates/account.html
@@ -0,0 +1,83 @@
+{% extends "layout.html" %}
+{% block content %}
+<div class="content-section">
+ <div class="media">
+ <img class="rounded-circle account-img" src="{{ image_file }}">
+ <div class="media-body">
+ <h2 class="account-heading">{{ current_user.username }}</h2>
+ <p class="text-secondary">{{ current_user.email }}</p>
+ </div>
+ </div>
+ <form method="POST" action="" enctype="multipart/form-data">
+ {{ form.hidden_tag() }}
+ <fieldset class="form-group">
+ <legend class="border-bottom mb-4">Informatie</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>
+ <div class="form-group">
+ {{ form.email.label(class="form-control-label") }}
+ {% if form.email.errors %}
+ {{ form.email(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.email.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.email(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ <div class="form-group">
+ {{ form.password.label(class="form-control-label") }}
+ {% if form.password.errors %}
+ {{ form.password(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.password.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.password(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ <div class="form-group">
+ {{ form.confirm_password.label(class="form-control-label") }}
+ {% if form.confirm_password.errors %}
+ {{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.confirm_password.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.confirm_password(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ <div class="form-group">
+ {{ form.picture.label() }}
+ {{ form.picture(class="form-control-file") }}
+ {% if form.picture.errors %}
+ {% for error in form.picture.errors %}
+ <span class="text-danger">{{ error }}</span></br>
+ {% endfor %}
+ {% 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/pgmles/templates/admin.html b/pgmles/templates/admin.html
@@ -0,0 +1,27 @@
+{% 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
@@ -0,0 +1,57 @@
+{% 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">×</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
@@ -0,0 +1,37 @@
+{% 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
@@ -0,0 +1,48 @@
+{% 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">×</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/index.html b/pgmles/templates/index.html
@@ -0,0 +1,32 @@
+{% extends "layout.html" %}
+{% block content %}
+<article class="media content-section">
+ <div class="media-body">
+ <h2>Dit zijn ons lessen</h2>
+ <p>
+ U kan makkelijk op de titel van een les klikken om in te schrijven!
+ </p>
+ </div>
+</article>
+{% for course in courses %}
+<article class="media content-section">
+ <div class="media-body">
+ <h3><a class="article-title" href="{{url_for('course', course_id=course.id)}}">
+ {{ course.name }}
+ {{ '(ingescheven)' if course.id in subs }}
+ </a></h3>
+ <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>
+</article>
+{% endfor %}
+{% endblock content %}
+\ No newline at end of file
diff --git a/pgmles/templates/layout.html b/pgmles/templates/layout.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
+ integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
+
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='openmoji/openmoji.css') }}">
+
+ {% if title %}
+ <title>Programmeerles voor ouderen - {{ title }}</title>
+ {% else %}
+ <title>Programmeerles voor ouderen</title>
+ {% endif %}
+</head>
+
+<body>
+ <header class="site-header">
+ <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
+ <div class="container">
+ <a class="navbar-brand mr-4" href="/">Programmeerles voor ouderen</a>
+ <div class="collapse navbar-collapse" id="navbarToggle">
+ <div class="navbar-nav mr-auto">
+ <a class="nav-item nav-link" href="{{ url_for('about') }}">Over ons</a>
+ </div>
+ <!-- Navbar Right Side -->
+ <div class="navbar-nav">
+ {% if current_user.is_authenticated %}
+ <a class="nav-item nav-link" href="{{ url_for('logout') }}">Uitloggen</a>
+ {% else %}
+ <a class="nav-item nav-link" href="{{ url_for('login') }}">Inloggen</a>
+ <a class="nav-item nav-link" href="{{ url_for('register') }}">Registeren</a>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ </nav>
+ </header>
+ <main role="main" class="container">
+ <div class="row">
+ <div class="col-md-8">
+ {% with messages = get_flashed_messages(with_categories=true) %}
+ {% if messages %}
+ {% for category, message in messages %}
+ <div class="alert alert-{{ category }}">
+ {{ message }}
+ </div>
+ {% endfor %}
+ {% endif %}
+ {% endwith %}
+ {% block content %}{% endblock %}
+ </div>
+ <div class="col-md-4">
+ <div class="content-section">
+ <h3>Welkom <b>{{ current_user.username if current_user.is_authenticated else 'gast' }}</b>!</h3>
+ {% if current_user.is_authenticated %}
+ <p class='text-muted'>
+ <ul class="list-group">
+ <li class="list-group-item list-group-item-light"><a
+ href="{{ url_for('account') }}">Instellingen</a></li>
+ {% if current_user.type == 'teacher' or current_user.type == 'admin' %}
+ <li class="list-group-item list-group-item-light"><a
+ href="{{ url_for('course_overview') }}">Lesoverzicht</a></li>
+ {% endif %}
+ {% if current_user.type == 'admin' %}
+ <li class="list-group-item list-group-item-light"><a href="{{ url_for('admin') }}">Profielen
+ bewerken</a></li>
+ {% endif %}
+ </ul>
+ </p>
+ {% endif %}
+ {% if current_user.is_authenticated %}
+ <table class='calendar'>
+ <tr>
+ {% for d, day in calendar['weekdays'] %}
+ <th class="{{ 'weekend' if d >= 5 }}">{{ day }}</th>
+ {% endfor %}
+ </tr>
+ {% for row in calendar['rows'] %}
+ <tr>
+ {% for d, day, course in row %}
+ <td class="{{ 'weekend' if d >= 5 }} {{ 'course' if course and day }}">
+ {{ day if day }}
+ {% if course %}<span class='hover-day'>{{ course }}</span>{% endif %}
+ </td>
+ {% endfor %}
+ </tr>
+ {% endfor %}
+ </table>
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ </main>
+
+ <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
+ integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
+ crossorigin="anonymous"></script>
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"
+ integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
+ crossorigin="anonymous"></script>
+ <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"
+ integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
+ crossorigin="anonymous"></script>
+</body>
+
+</html>
+\ No newline at end of file
diff --git a/pgmles/templates/login.html b/pgmles/templates/login.html
@@ -0,0 +1,52 @@
+{% 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">Inloggen</legend>
+ <div class="form-group">
+ {{ form.email.label(class="form-control-label") }}
+ {% if form.email.errors %}
+ {{ form.email(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.email.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.email(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ <div class="form-group">
+ {{ form.password.label(class="form-control-label") }}
+ {% if form.password.errors %}
+ {{ form.password(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.password.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.password(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ <div class="form-check">
+ {{ form.remember(class="form-check-input") }}
+ {{ form.remember.label(class="form-check-label") }}
+ </div>
+ </fieldset>
+ <div class="form-group">
+ {{ form.submit(class="btn btn-outline-info") }}
+ </div>
+ <small class="text-muted ml-2">
+ Wachtwoord vergeten? Neem contact met een administrator.
+ </small>
+ </form>
+</div>
+<div class="border-top pt-3">
+ <small class="text-muted">
+ Een profiel nodig? <a class="ml-2" href="{{ url_for('register') }}">Nu inschrijven!</a>
+ </small>
+</div>
+{% endblock content %}
+\ No newline at end of file
diff --git a/pgmles/templates/new_course.html b/pgmles/templates/new_course.html
@@ -0,0 +1,105 @@
+{% 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/pgmles/templates/register.html b/pgmles/templates/register.html
@@ -0,0 +1,71 @@
+{% 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">Nog vandaag meedoen!</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>
+ <div class="form-group">
+ {{ form.email.label(class="form-control-label") }}
+ {% if form.email.errors %}
+ {{ form.email(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.email.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.email(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ <div class="form-group">
+ {{ form.password.label(class="form-control-label") }}
+ {% if form.password.errors %}
+ {{ form.password(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.password.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.password(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ <div class="form-group">
+ {{ form.confirm_password.label(class="form-control-label") }}
+ {% if form.confirm_password.errors %}
+ {{ form.confirm_password(class="form-control form-control-lg is-invalid") }}
+ <div class="invalid-feedback">
+ {% for error in form.confirm_password.errors %}
+ <span>{{ error }}</span>
+ {% endfor %}
+ </div>
+ {% else %}
+ {{ form.confirm_password(class="form-control form-control-lg") }}
+ {% endif %}
+ </div>
+ </fieldset>
+ <div class="form-group">
+ {{ form.submit(class="btn btn-outline-info") }}
+ </div>
+ </form>
+</div>
+<div class="border-top pt-3">
+ <small class="text-muted">Heb jij al een account? <a class="ml-2"
+ href="{{ url_for('login') }}">Inloggen!</a></small>
+</div>
+{% endblock content %}
+\ No newline at end of file
diff --git a/readme.md b/readme.md
@@ -0,0 +1,79 @@
+# PROGRAMMEERLES VOOR OUDEREN
+
+[Repository](https://github.com/MoiBaguette/Webtechnologie-Project)
+
+## 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
+```
+
+**Alle afhankelijkheden installeren:**
+```
+$ pip3 install flask wtforms flask_sqlalchemy flask-wtf email_validator flask-bcrypt flask-login pillow
+```
+
+**De server runnen:**
+```
+$ python run.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
@@ -0,0 +1,4 @@
+from pgmles.server import app
+
+if __name__ == '__main__':
+ app.run(debug=True)
diff --git a/server/app.py b/server/app.py
@@ -0,0 +1 @@
+import flask
diff --git a/test-client.py b/test-client.py