hanze/programmeerles-ouderen

finished tutorial and start building own app (16d772e9373cfe1b787a09fee9e6a0e7e34f73db)
Repositories

commit 16d772e9373cfe1b787a09fee9e6a0e7e34f73db
Author: gerco <[email protected]>
Date:   Sun,  3 Apr 2022 22:31:55 +0200

finished tutorial and start building own app

Diffstat:
A.gitignore2++
Aflaskblog/__init__.py15+++++++++++++++
Aflaskblog/forms.py60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/models.py43+++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/routes.py147+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/site.db0
Aflaskblog/static/main.css80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/static/profile_pics/2e32b4c96a8d8f10.jpg0
Aflaskblog/static/profile_pics/4895353c1045b870.jpg0
Aflaskblog/static/profile_pics/4c4083a8362b3c0a.jpg0
Aflaskblog/static/profile_pics/53998538edf2342c.png0
Aflaskblog/static/profile_pics/6809465dffb2e5ba.jpg0
Aflaskblog/static/profile_pics/7798432669b8b3ac.jpg0
Aflaskblog/static/profile_pics/85ed1b444539873d.png0
Aflaskblog/static/profile_pics/b6e1c53325f88b74.png0
Aflaskblog/static/profile_pics/default.jpg0
Aflaskblog/templates/about.html4++++
Aflaskblog/templates/account.html57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/templates/create_post.html40++++++++++++++++++++++++++++++++++++++++
Aflaskblog/templates/home.html10++++++++++
Aflaskblog/templates/home1.html27+++++++++++++++++++++++++++
Aflaskblog/templates/layout.html84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/templates/login.html52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/templates/post.html41+++++++++++++++++++++++++++++++++++++++++
Aflaskblog/templates/register.html72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aflaskblog/templates/user_post.html28++++++++++++++++++++++++++++
Areadme.md4++++
Arun.py4++++
28 files changed, 770 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +__pycache__ +\ No newline at end of file diff --git a/flaskblog/__init__.py b/flaskblog/__init__.py @@ -0,0 +1,15 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy +from flask_bcrypt import Bcrypt +from flask_login import LoginManager + +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' + +from flaskblog import routes diff --git a/flaskblog/forms.py b/flaskblog/forms.py @@ -0,0 +1,59 @@ +from flask_wtf import FlaskForm +from flask_wtf.file import FileField, FileAllowed +from flask_login import current_user +from wtforms import StringField, PasswordField, SubmitField, BooleanField, TextAreaField +from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError +from flaskblog.models import User + + +class RegistrationForm(FlaskForm): + username = StringField('Username', + validators=[DataRequired(), Length(min=2, max=20)]) + email = StringField('Email', + validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired()]) + confirm_password = PasswordField('Confirm Password', + validators=[DataRequired(), EqualTo('password')]) + submit = SubmitField('Sign Up') + + def validate_username(self, username): + user = User.query.filter_by(username=username.data).first() + if user: + raise ValidationError('That username is taken. Please choose a different one.') + + def validate_email(self, email): + user = User.query.filter_by(email=email.data).first() + if user: + raise ValidationError('That email is taken. Please choose a different one.') + + +class LoginForm(FlaskForm): + email = StringField('Email', + validators=[DataRequired(), Email()]) + password = PasswordField('Password', validators=[DataRequired()]) + remember = BooleanField('Remember Me') + submit = SubmitField('Login') + +class UpdateAccountForm(FlaskForm): + username = StringField('Username', + validators=[DataRequired(), Length(min=2, max=20)]) + email = StringField('Email', + validators=[DataRequired(), Email()]) + picture = FileField('Update Profile Picture',validators=[FileAllowed(['jpg','png'])]) + submit = SubmitField('Update') + + def validate_username(self, username): + if username.data != current_user.username: + user = User.query.filter_by(username=username.data).first() + if user: + raise ValidationError('That username is taken. Please choose a different one.') + + def validate_email(self, email): + if email.data != current_user.email: + user = User.query.filter_by(email=email.data).first() + if user: + raise ValidationError('That email is taken. Please choose a different one.') +class PostForm(FlaskForm): + title = StringField('Title', validators=[DataRequired()]) + content = TextAreaField('Content', validators=[DataRequired()]) + submit = SubmitField('Post') +\ No newline at end of file diff --git a/flaskblog/models.py b/flaskblog/models.py @@ -0,0 +1,43 @@ +from datetime import datetime +from flaskblog import db, login_manager +from flask_login import UserMixin + + +@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) + 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) + + def __repr__(self): + return f"User('{self.username}', '{self.email}', '{self.image_file}')" + +class Language(db.Model): + id = db.Column(db.Integer, primary_key=True) + language = db.Column(db.String(100), nullable=False) + + def __repr__(self): + return f"Language('{self.language}')" + +class Classes(db.Model): + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + language_id = db.Column(db.Integer, db.ForeignKey('language.id'), nullable=False) + teacher_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + start = db.Column(db.DateTime, nullable=False) + location = db.Column(db.String(120), nullable=False) + + def __repr__(self): + return f"Language('{self.id}', '{self.language_id}', '{self.start}', '{self.location}')" + + + +# date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) +# content = db.Column(db.Text, nullable=False) +# user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) diff --git a/flaskblog/routes.py b/flaskblog/routes.py @@ -0,0 +1,147 @@ +import os +import secrets +from PIL import Image +from flask import render_template, url_for, flash, redirect, request, abort +from flaskblog import app, db, bcrypt +from flaskblog.forms import RegistrationForm, LoginForm, UpdateAccountForm, PostForm +from flaskblog.models import User, Language, Classes +from flask_login import login_user, current_user, logout_user, login_required + + [email protected]("/") [email protected]("/home") +def home(): + page = request.args.get('page', 1, type=int) + languages = Language.query.all() + return render_template('home.html', languages=languages) + + [email protected]("/about") +def about(): + return render_template('about.html', title='About') + + [email protected]("/register", methods=['GET', 'POST']) +def register(): + if current_user.is_authenticated: + return redirect(url_for('home')) + 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('Your account has been created! You are now able to log in', 'success') + return redirect(url_for('login')) + return render_template('register.html', title='Register', form=form) + + [email protected]("/login", methods=['GET', 'POST']) +def login(): + if current_user.is_authenticated: + return redirect(url_for('home')) + 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) + next_page = request.args.get('next') + return redirect(next_page) if next_page else redirect(url_for('home')) + else: + flash('Login Unsuccessful. Please check email and password', 'danger') + return render_template('login.html', title='Login', form=form) + + [email protected]("/logout") +def logout(): + logout_user() + return redirect(url_for('home')) + +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 + [email protected]("/account", methods=['GET', 'POST']) +@login_required +def account(): + form = UpdateAccountForm() + if form.validate_on_submit(): + if form.picture.data: + picture_file = save_picture(form.picture.data) + current_user.image_file = picture_file + current_user.username = form.username.data + current_user.email = form.email.data + db.session.commit() + flash('Your account has been updated!', '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', title='Account', image_file=image_file, form=form) + [email protected]("/post/new", methods=['GET', 'POST']) +@login_required +def new_post(): + form = PostForm() + if form.validate_on_submit(): + #post = Post(title=form.title.data, content=form.content.data, author=current_user) + db.session.add(post) + db.session.commit() + flash('Your post has been created!', 'success') + return redirect(url_for('home')) + return render_template('create_post.html', title='New Post', + form=form, legend='New Post') + [email protected]("/post/<int:post_id>") +def post(post_id): + #post = Post.query.get_or_404(post_id) + return render_template('post.html', title=post.title, post=post) + [email protected]("/post/<int:post_id>/update", methods=['GET', 'POST']) +@login_required +def update_post(post_id): + #post = Post.query.get_or_404(post_id) + if post.author != current_user: + abort(403) + form = PostForm() + if form.validate_on_submit(): + post.title = form.title.data + post.content = form.content.data + db.session.commit() + flash('Your post has been updated!', 'success') + return redirect(url_for('post', post_id=post.id)) + elif request.method == 'GET': + form.title.data = post.title + form.content.data = post.content + return render_template('create_post.html', title='Update Post', + form=form, legend='Update Post') + [email protected]("/post/<int:post_id>/delete", methods=['POST']) +@login_required +def delete_post(post_id): + #post = Post.query.get_or_404(post_id) + if post.author != current_user: + abort(403) + db.session.delete(post) + db.session.commit() + flash('Your post has been deleted!', 'success') + return redirect(url_for('home')) + [email protected]("/user/<string:username>") +def user_posts(username): + page = request.args.get('page', 1, type=int) + user = User.query.filter_by(username=username).first_or_404() + #posts = Post.query.filter_by(author=user)\ + # .order_by(Post.date_posted.desc())\ + #.paginate(page=page, per_page=5) + #return render_template('user_post.html', posts=posts, user=user) diff --git a/flaskblog/site.db b/flaskblog/site.db Binary files differ. diff --git a/flaskblog/static/main.css b/flaskblog/static/main.css @@ -0,0 +1,80 @@ +body { + background: #fafafa; + color: #333333; + margin-top: 5rem; +} + +h1, h2, h3, h4, h5, h6 { + color: #444444; +} + +.bg-steel { + background-color: #5f788a; +} + +.site-header .navbar-nav .nav-link { + color: #cbd5db; +} + +.site-header .navbar-nav .nav-link:hover { + color: #ffffff; +} + +.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; +} diff --git a/flaskblog/static/profile_pics/2e32b4c96a8d8f10.jpg b/flaskblog/static/profile_pics/2e32b4c96a8d8f10.jpg Binary files differ. diff --git a/flaskblog/static/profile_pics/4895353c1045b870.jpg b/flaskblog/static/profile_pics/4895353c1045b870.jpg Binary files differ. diff --git a/flaskblog/static/profile_pics/4c4083a8362b3c0a.jpg b/flaskblog/static/profile_pics/4c4083a8362b3c0a.jpg Binary files differ. diff --git a/flaskblog/static/profile_pics/53998538edf2342c.png b/flaskblog/static/profile_pics/53998538edf2342c.png Binary files differ. diff --git a/flaskblog/static/profile_pics/6809465dffb2e5ba.jpg b/flaskblog/static/profile_pics/6809465dffb2e5ba.jpg Binary files differ. diff --git a/flaskblog/static/profile_pics/7798432669b8b3ac.jpg b/flaskblog/static/profile_pics/7798432669b8b3ac.jpg Binary files differ. diff --git a/flaskblog/static/profile_pics/85ed1b444539873d.png b/flaskblog/static/profile_pics/85ed1b444539873d.png Binary files differ. diff --git a/flaskblog/static/profile_pics/b6e1c53325f88b74.png b/flaskblog/static/profile_pics/b6e1c53325f88b74.png Binary files differ. diff --git a/flaskblog/static/profile_pics/default.jpg b/flaskblog/static/profile_pics/default.jpg Binary files differ. diff --git a/flaskblog/templates/about.html b/flaskblog/templates/about.html @@ -0,0 +1,4 @@ +{% extends "layout.html" %} +{% block content %} + <h1>About Page</h1> +{% endblock content %} diff --git a/flaskblog/templates/account.html b/flaskblog/templates/account.html @@ -0,0 +1,57 @@ +{% 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">Account Info</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.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 %} diff --git a/flaskblog/templates/create_post.html b/flaskblog/templates/create_post.html @@ -0,0 +1,40 @@ +{% 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">{{ legend }}</legend> + <div class="form-group"> + {{ form.title.label(class="form-control-label") }} + {% if form.title.errors %} + {{ form.title(class="form-control form-control-lg is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.title.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form.title(class="form-control form-control-lg") }} + {% endif %} + </div> + <div class="form-group"> + {{ form.content.label(class="form-control-label") }} + {% if form.content.errors %} + {{ form.content(class="form-control form-control-lg is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.content.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form.content(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 %} diff --git a/flaskblog/templates/home.html b/flaskblog/templates/home.html @@ -0,0 +1,10 @@ +{% extends "layout.html" %} +{% block content %} + {% for name in languages %} + <article class="media content-section"> + <div class="media-body"> + <h1>{{name.language}}</h1> + </div> + </article> + {% endfor %} +{% endblock content %} diff --git a/flaskblog/templates/home1.html b/flaskblog/templates/home1.html @@ -0,0 +1,27 @@ +{% extends "layout.html" %} +{% block content %} + {% for language in language.items %} + <article class="media content-section"> + <!-- <img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}">--> + <div class="media-body"> + <div class="article-metadata"> + <!--<a class="mr-2" href="{{ url_for('user_posts', username=language.author.username)}}">{{ post.author.username }}</a> + <small class="text-muted">{{ post.date_posted.strftime('%Y-%m-%d') }}</small>--> + </div> + <!--<h2><a class="article-title" href="{{ url_for('post', post_id=post.id) }}">{{ post.title }}</a></h2>--> + <!--<p class="article-content">{{ post.content }}</p>--> + </div> + </article> + {% endfor %} + <!--{% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %} + {% if page_num %} + {% if posts.page == page_num%} + <a class="btn btn-info mb-4"href="{{url_for('home', page=page_num)}}">{{page_num}}</a> + {% else %} + <a class="btn btn-outline-info mb-4"href="{{url_for('home', page=page_num)}}">{{page_num}}</a> + {% endif %} + {% else %} + ... + {% endif %} + {% endfor %}--> +{% endblock content %} diff --git a/flaskblog/templates/layout.html b/flaskblog/templates/layout.html @@ -0,0 +1,84 @@ +<!DOCTYPE html> +<html> +<head> + <!-- Required meta tags --> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> + + <!-- Bootstrap CSS --> + <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') }}"> + + {% if title %} + <title>Flask Blog - {{ title }}</title> + {% else %} + <title>Flask Blog</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="/">Flask Blog</a> + <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation"> + <span class="navbar-toggler-icon"></span> + </button> + <div class="collapse navbar-collapse" id="navbarToggle"> + <div class="navbar-nav mr-auto"> + <a class="nav-item nav-link" href="{{ url_for('home') }}">Home</a> + <a class="nav-item nav-link" href="{{ url_for('about') }}">About</a> + </div> + <!-- Navbar Right Side --> + <div class="navbar-nav"> + {% if current_user.is_authenticated %} + <a class="nav-item nav-link" href="{{ url_for('new_post') }}">New Post</a> + <a class="nav-item nav-link" href="{{ url_for('account') }}">Account</a> + <a class="nav-item nav-link" href="{{ url_for('logout') }}">Logout</a> + {% else %} + <a class="nav-item nav-link" href="{{ url_for('login') }}">Login</a> + <a class="nav-item nav-link" href="{{ url_for('register') }}">Register</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>Our Sidebar</h3> + <p class='text-muted'>You can put any information here you'd like. + <ul class="list-group"> + <li class="list-group-item list-group-item-light">Latest Posts</li> + <li class="list-group-item list-group-item-light">Announcements</li> + <li class="list-group-item list-group-item-light">Calendars</li> + <li class="list-group-item list-group-item-light">etc</li> + </ul> + </p> + </div> + </div> + </div> + </main> + + + <!-- Optional JavaScript --> + <!-- jQuery first, then Popper.js, then Bootstrap JS --> + <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> diff --git a/flaskblog/templates/login.html b/flaskblog/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">Log In</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"> + <a href="#">Forgot Password?</a> + </small> + </form> + </div> + <div class="border-top pt-3"> + <small class="text-muted"> + Need An Account? <a class="ml-2" href="{{ url_for('register') }}">Sign Up Now</a> + </small> + </div> +{% endblock content %} diff --git a/flaskblog/templates/post.html b/flaskblog/templates/post.html @@ -0,0 +1,41 @@ +{% extends "layout.html" %} +{% block content %} + <article class="media content-section"> + <img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}"> + <div class="media-body"> + <div class="article-metadata"> + <a class="mr-2" href="{{ url_for('user_posts', username=post.author.username)}}">{{ post.author.username }}</a> + <small class="text-muted">{{ post.date_posted.strftime('%Y-%m-%d') }}</small> + {% if post.author == current_user %} + <div> + <a class="btn btn-secondary btn-sm mt-1 mb-1" href="{{ url_for('update_post', post_id=post.id)}}">Update</a> + <button type="button" class="btn btn-danger btn-sm m-1" data-toggle="modal" data-target="#deletemodal">Delete</button> + </div> + {% endif %} + </div> + <h2 class="article-title">{{ post.title }}</h2> + <p class="article-content">{{ post.content }}</p> + </div> + </article> + <!-- Modal --> + <div class="modal fade" id="deletemodal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> + <div class="modal-dialog" role="document"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="deletemodal">Delete Post?</h5> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">&times;</span> + </button> + </div> + <div class="modal-body"> + Are you sure you want to delete this post? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> + <form action="{{ url_for('delete_post', post_id=post.id) }}" method="POST"> + <input class="btn btn-danger" type="submit" value="Delete"> + </div> + </div> + </div> + </div> +{% endblock content %} diff --git a/flaskblog/templates/register.html b/flaskblog/templates/register.html @@ -0,0 +1,72 @@ +{% 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">Join Today</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"> + Already Have An Account? <a class="ml-2" href="{{ url_for('login') }}">Sign In</a> + </small> + </div> +{% endblock content %} diff --git a/flaskblog/templates/user_post.html b/flaskblog/templates/user_post.html @@ -0,0 +1,28 @@ +{% extends "layout.html" %} +{% block content %} + <h1 class="mb-3">Posts by {{ user.username }} ({{ posts.total }})</h1> + {% for post in posts.items %} + <article class="media content-section"> + <img class="rounded-circle article-img" src="{{ url_for('static', filename='profile_pics/' + post.author.image_file) }}"> + <div class="media-body"> + <div class="article-metadata"> + <a class="mr-2" href="{{ url_for('user_posts', username=post.author.username)}}">{{ post.author.username }}</a> + <small class="text-muted">{{ post.date_posted.strftime('%Y-%m-%d') }}</small> + </div> + <h2><a class="article-title" href="{{ url_for('post', post_id=post.id) }}">{{ post.title }}</a></h2> + <p class="article-content">{{ post.content }}</p> + </div> + </article> + {% endfor %} + {% for page_num in posts.iter_pages(left_edge=1, right_edge=1, left_current=1, right_current=2) %} + {% if page_num %} + {% if posts.page == page_num%} + <a class="btn btn-info mb-4"href="{{url_for('user_posts', username=user.username, page=page_num)}}">{{page_num}}</a> + {% else %} + <a class="btn btn-outline-info mb-4" href="{{url_for('user_posts', username=user.username, page=page_num)}}">{{page_num}}</a> + {% endif %} + {% else %} + ... + {% endif %} + {% endfor %} +{% endblock content %} diff --git a/readme.md b/readme.md @@ -0,0 +1,3 @@ +```pip3 install flask wtforms flask_sqlalchemy flask-wtf email_validator flask-bcrypt flask-login Pillow``` + +```python3 run.py``` +\ No newline at end of file diff --git a/run.py b/run.py @@ -0,0 +1,4 @@ +from flaskblog import app + +if __name__ == '__main__': + app.run(debug=True)