Skip to content

Commit 83431aa

Browse files
author
Lenar Gasimov
committed
feat: added complete day 69
Upgraded our blog to add authentication for new users and allow any user to add comment to blog posts.
1 parent 864dd04 commit 83431aa

File tree

89 files changed

+69527
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+69527
-1
lines changed

Diff for: README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,5 @@ I'll be using this repo as a way for myself to access them as, if and when I nee
8686
- [Day 65](day65): Web Design School - How to Create a Website that People will Love
8787
- [Day 66](day66): Building Your Own API with RESTful Routing
8888
- [Day 67](day67): Blog Capstone Project Part 3 - RESTful Routing
89-
- [Day 68](day68): Authentication with Flask
89+
- [Day 68](day68): Authentication with Flask
90+
- [Day 69](day69): Blog Capstone Project Part 4 - Adding Users

Diff for: day68/flask-auth-end/users.db

0 Bytes
Binary file not shown.

Diff for: day69/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Day 69
2+
3+
Upgraded our blog to add authentication for new users and allow any user to add comment to blog posts.
4+
5+
##Adding Users
6+
7+
![comment](comment.gif)

Diff for: day69/blog-with-users/blog.db

24 KB
Binary file not shown.

Diff for: day69/blog-with-users/forms.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from flask_wtf import FlaskForm
2+
from wtforms import StringField, SubmitField, PasswordField
3+
from wtforms.validators import DataRequired, URL
4+
from flask_ckeditor import CKEditorField
5+
6+
7+
##WTForm
8+
class CreatePostForm(FlaskForm):
9+
title = StringField("Blog Post Title", validators=[DataRequired()])
10+
subtitle = StringField("Subtitle", validators=[DataRequired()])
11+
img_url = StringField("Blog Image URL", validators=[DataRequired(), URL()])
12+
body = CKEditorField("Blog Content", validators=[DataRequired()])
13+
submit = SubmitField("Submit Post")
14+
15+
16+
class RegisterForm(FlaskForm):
17+
email = StringField("Email", validators=[DataRequired()])
18+
password = PasswordField("Password", validators=[DataRequired()])
19+
name = StringField("Name", validators=[DataRequired()])
20+
submit = SubmitField("Sign Me Up!")
21+
22+
23+
class LoginForm(FlaskForm):
24+
email = StringField("Email", validators=[DataRequired()])
25+
password = PasswordField("Password", validators=[DataRequired()])
26+
submit = SubmitField("Let Me In!")
27+
28+
29+
class CommentForm(FlaskForm):
30+
comment_text = CKEditorField("Comment", validators=[DataRequired()])
31+
submit = SubmitField("Submit Comment")

Diff for: day69/blog-with-users/main.py

+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
from flask import Flask, render_template, redirect, url_for, flash, abort
2+
from flask_bootstrap import Bootstrap
3+
from flask_ckeditor import CKEditor
4+
from datetime import date
5+
from functools import wraps
6+
from werkzeug.security import generate_password_hash, check_password_hash
7+
from flask_sqlalchemy import SQLAlchemy
8+
from sqlalchemy.orm import relationship
9+
from flask_login import UserMixin, login_user, LoginManager, login_required, current_user, logout_user
10+
from forms import LoginForm, RegisterForm, CreatePostForm, CommentForm
11+
from flask_gravatar import Gravatar
12+
13+
app = Flask(__name__)
14+
app.config['SECRET_KEY'] = '8BYkEfBA6O6donzWlSihBXox7C0sKR6b'
15+
ckeditor = CKEditor(app)
16+
Bootstrap(app)
17+
gravatar = Gravatar(app, size=100, rating='g', default='retro', force_default=False, force_lower=False, use_ssl=False, base_url=None)
18+
19+
##CONNECT TO DB
20+
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
21+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
22+
db = SQLAlchemy(app)
23+
login_manager = LoginManager()
24+
login_manager.init_app(app)
25+
26+
27+
@login_manager.user_loader
28+
def load_user(user_id):
29+
return User.query.get(int(user_id))
30+
31+
32+
##CONFIGURE TABLE
33+
class User(UserMixin, db.Model):
34+
__tablename__ = "users"
35+
id = db.Column(db.Integer, primary_key=True)
36+
email = db.Column(db.String(100), unique=True)
37+
password = db.Column(db.String(100))
38+
name = db.Column(db.String(100))
39+
posts = relationship("BlogPost", back_populates="author")
40+
comments = relationship("Comment", back_populates="comment_author")
41+
42+
43+
class BlogPost(db.Model):
44+
__tablename__ = "blog_posts"
45+
id = db.Column(db.Integer, primary_key=True)
46+
author_id = db.Column(db.Integer, db.ForeignKey("users.id"))
47+
author = relationship("User", back_populates="posts")
48+
title = db.Column(db.String(250), unique=True, nullable=False)
49+
subtitle = db.Column(db.String(250), nullable=False)
50+
date = db.Column(db.String(250), nullable=False)
51+
body = db.Column(db.Text, nullable=False)
52+
img_url = db.Column(db.String(250), nullable=False)
53+
comments = relationship("Comment", back_populates="parent_post")
54+
55+
56+
class Comment(db.Model):
57+
__tablename__ = "comments"
58+
id = db.Column(db.Integer, primary_key=True)
59+
post_id = db.Column(db.Integer, db.ForeignKey("blog_posts.id"))
60+
author_id = db.Column(db.Integer, db.ForeignKey("users.id"))
61+
parent_post = relationship("BlogPost", back_populates="comments")
62+
comment_author = relationship("User", back_populates="comments")
63+
text = db.Column(db.Text, nullable=False)
64+
db.create_all()
65+
66+
67+
def admin_only(f):
68+
@wraps(f)
69+
def decorated_function(*args, **kwargs):
70+
if current_user.id != 1:
71+
return abort(403)
72+
return f(*args, **kwargs)
73+
return decorated_function
74+
75+
76+
@app.route('/')
77+
def get_all_posts():
78+
posts = BlogPost.query.all()
79+
return render_template("index.html", all_posts=posts, current_user=current_user)
80+
81+
82+
@app.route('/register', methods=["GET", "POST"])
83+
def register():
84+
form = RegisterForm()
85+
if form.validate_on_submit():
86+
87+
if User.query.filter_by(email=form.email.data).first():
88+
print(User.query.filter_by(email=form.email.data).first())
89+
#User already exists
90+
flash("You've already signed up with that email, log in instead!")
91+
return redirect(url_for('login'))
92+
93+
hash_and_salted_password = generate_password_hash(
94+
form.password.data,
95+
method='pbkdf2:sha256',
96+
salt_length=8
97+
)
98+
new_user = User(
99+
email=form.email.data,
100+
name=form.name.data,
101+
password=hash_and_salted_password,
102+
)
103+
db.session.add(new_user)
104+
db.session.commit()
105+
login_user(new_user)
106+
return redirect(url_for("get_all_posts"))
107+
108+
return render_template("register.html", form=form, current_user=current_user)
109+
110+
111+
@app.route('/login', methods=["GET", "POST"])
112+
def login():
113+
form = LoginForm()
114+
if form.validate_on_submit():
115+
email = form.email.data
116+
password = form.password.data
117+
118+
user = User.query.filter_by(email=email).first()
119+
# Email doesn't exist or password incorrect.
120+
if not user:
121+
flash("That email does not exist, please try again.")
122+
return redirect(url_for('login'))
123+
elif not check_password_hash(user.password, password):
124+
flash('Password incorrect, please try again.')
125+
return redirect(url_for('login'))
126+
else:
127+
login_user(user)
128+
return redirect(url_for('get_all_posts'))
129+
return render_template("login.html", form=form, current_user=current_user)
130+
131+
132+
@app.route('/logout')
133+
def logout():
134+
logout_user()
135+
return redirect(url_for('get_all_posts'))
136+
137+
138+
@app.route("/post/<int:post_id>", methods=["GET", "POST"])
139+
def show_post(post_id):
140+
form = CommentForm()
141+
requested_post = BlogPost.query.get(post_id)
142+
143+
if form.validate_on_submit():
144+
if not current_user.is_authenticated:
145+
flash("You need to login or register to comment.")
146+
return redirect(url_for("login"))
147+
148+
new_comment = Comment(
149+
text=form.comment_text.data,
150+
comment_author=current_user,
151+
parent_post=requested_post
152+
)
153+
db.session.add(new_comment)
154+
db.session.commit()
155+
156+
return render_template("post.html", post=requested_post, form=form, current_user=current_user)
157+
158+
159+
@app.route("/about")
160+
def about():
161+
return render_template("about.html", current_user=current_user)
162+
163+
164+
@app.route("/contact")
165+
def contact():
166+
return render_template("contact.html", current_user=current_user)
167+
168+
169+
@app.route("/new-post", methods=["GET", "POST"])
170+
@admin_only
171+
def add_new_post():
172+
form = CreatePostForm()
173+
if form.validate_on_submit():
174+
new_post = BlogPost(
175+
title=form.title.data,
176+
subtitle=form.subtitle.data,
177+
body=form.body.data,
178+
img_url=form.img_url.data,
179+
author=current_user,
180+
date=date.today().strftime("%B %d, %Y")
181+
)
182+
db.session.add(new_post)
183+
db.session.commit()
184+
return redirect(url_for("get_all_posts"))
185+
186+
return render_template("make-post.html", form=form, current_user=current_user)
187+
188+
189+
190+
191+
@app.route("/edit-post/<int:post_id>", methods=["GET", "POST"])
192+
@admin_only
193+
def edit_post(post_id):
194+
post = BlogPost.query.get(post_id)
195+
edit_form = CreatePostForm(
196+
title=post.title,
197+
subtitle=post.subtitle,
198+
img_url=post.img_url,
199+
author=current_user,
200+
body=post.body
201+
)
202+
if edit_form.validate_on_submit():
203+
post.title = edit_form.title.data
204+
post.subtitle = edit_form.subtitle.data
205+
post.img_url = edit_form.img_url.data
206+
post.body = edit_form.body.data
207+
db.session.commit()
208+
return redirect(url_for("show_post", post_id=post.id))
209+
210+
return render_template("make-post.html", form=edit_form, is_edit=True, current_user=current_user)
211+
212+
213+
@app.route("/delete/<int:post_id>")
214+
@admin_only
215+
def delete_post(post_id):
216+
post_to_delete = BlogPost.query.get(post_id)
217+
db.session.delete(post_to_delete)
218+
db.session.commit()
219+
return redirect(url_for('get_all_posts'))
220+
221+
222+
if __name__ == "__main__":
223+
app.run(host='0.0.0.0', port=5000)

Diff for: day69/blog-with-users/requirements.txt

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
certifi==2020.6.20
2+
chardet==3.0.4
3+
click==7.1.2
4+
dominate==2.5.2
5+
Flask==1.1.2
6+
Flask-Bootstrap==3.3.7.1
7+
Flask-CKEditor==0.4.4.1
8+
Flask-Gravatar==0.5.0
9+
Flask-Login==0.5.0
10+
Flask-SQLAlchemy==2.4.4
11+
Flask-WTF==0.14.3
12+
idna==2.10
13+
itsdangerous==1.1.0
14+
Jinja2==2.11.2
15+
MarkupSafe==1.1.1
16+
requests==2.24.0
17+
SQLAlchemy==1.3.19
18+
urllib3==1.25.10
19+
visitor==0.1.3
20+
Werkzeug==1.0.1
21+
WTForms==2.3.3

0 commit comments

Comments
 (0)