Skip to content

Commit eabf88f

Browse files
committed
added project base
0 parents  commit eabf88f

32 files changed

+739
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
**__pycache__

app.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from puppycompanyblog import app
2+
import os
3+
4+
if __name__ == '__main__':
5+
port = int(os.environ.get('PORT', 5000))
6+
app.run(port=port, debug=True)

puppycompanyblog/__init__.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
from flask import Flask
3+
from flask_sqlalchemy import SQLAlchemy
4+
from flask_migrate import Migrate
5+
from flask_login import LoginManager
6+
7+
8+
app = Flask(__name__)
9+
10+
#############################################################################
11+
############ CONFIGURATIONS (CAN BE SEPARATE CONFIG.PY FILE) ###############
12+
###########################################################################
13+
14+
# Remember you need to set your environment variables at the command line
15+
# when you deploy this to a real website.
16+
# export SECRET_KEY=mysecret
17+
# set SECRET_KEY=mysecret
18+
app.config['SECRET_KEY'] = 'mysecret'
19+
20+
#################################
21+
### DATABASE SETUPS ############
22+
###############################
23+
24+
basedir = os.path.abspath(os.path.dirname(__file__))
25+
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'data.sqlite')
26+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
27+
28+
29+
db = SQLAlchemy(app)
30+
Migrate(app,db)
31+
32+
33+
###########################
34+
#### LOGIN CONFIGS #######
35+
#########################
36+
37+
login_manager = LoginManager()
38+
39+
# We can now pass in our app to the login manager
40+
login_manager.init_app(app)
41+
42+
# Tell users what view to go to when they need to login.
43+
login_manager.login_view = "users.login"
44+
45+
46+
###########################
47+
#### BLUEPRINT CONFIGS #######
48+
#########################
49+
50+
# Import these at the top if you want
51+
# We've imported them here for easy reference
52+
from puppycompanyblog.core.views import core
53+
from puppycompanyblog.users.views import users
54+
from puppycompanyblog.blog_posts.views import blog_posts
55+
from puppycompanyblog.error_pages.handlers import error_pages
56+
57+
# Register the apps
58+
app.register_blueprint(users)
59+
app.register_blueprint(blog_posts)
60+
app.register_blueprint(core)
61+
app.register_blueprint(error_pages)

puppycompanyblog/blog_posts/__init__.py

Whitespace-only changes.

puppycompanyblog/blog_posts/forms.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from flask_wtf import FlaskForm
2+
from wtforms import StringField, SubmitField, TextAreaField
3+
from wtforms.validators import DataRequired
4+
5+
6+
class BlogPostForm(FlaskForm):
7+
# no empty titles or text possible
8+
# we'll grab the date automatically from the Model later
9+
title = StringField('Title', validators=[DataRequired()])
10+
text = TextAreaField('Text', validators=[DataRequired()])
11+
submit = SubmitField('BlogPost')

puppycompanyblog/blog_posts/views.py

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from flask import render_template,url_for,flash, redirect,request,Blueprint
2+
from flask_login import current_user,login_required
3+
from puppycompanyblog import db
4+
from puppycompanyblog.models import BlogPost
5+
from puppycompanyblog.blog_posts.forms import BlogPostForm
6+
7+
blog_posts = Blueprint('blog_posts',__name__)
8+
9+
@blog_posts.route('/create',methods=['GET','POST'])
10+
@login_required
11+
def create_post():
12+
form = BlogPostForm()
13+
14+
if form.validate_on_submit():
15+
16+
blog_post = BlogPost(title=form.title.data,
17+
text=form.text.data,
18+
user_id=current_user.id
19+
)
20+
db.session.add(blog_post)
21+
db.session.commit()
22+
flash("Blog Post Created")
23+
return redirect(url_for('core.index'))
24+
25+
return render_template('create_post.html',form=form)
26+
27+
28+
# int: makes sure that the blog_post_id gets passed as in integer
29+
# instead of a string so we can look it up later.
30+
@blog_posts.route('/<int:blog_post_id>')
31+
def blog_post(blog_post_id):
32+
# grab the requested blog post by id number or return 404
33+
blog_post = BlogPost.query.get_or_404(blog_post_id)
34+
return render_template('blog_post.html',title=blog_post.title,
35+
date=blog_post.date,post=blog_post
36+
)
37+
38+
@blog_posts.route("/<int:blog_post_id>/update", methods=['GET', 'POST'])
39+
@login_required
40+
def update(blog_post_id):
41+
blog_post = BlogPost.query.get_or_404(blog_post_id)
42+
if blog_post.author != current_user:
43+
# Forbidden, No Access
44+
abort(403)
45+
46+
form = BlogPostForm()
47+
if form.validate_on_submit():
48+
blog_post.title = form.title.data
49+
blog_post.text = form.text.data
50+
db.session.commit()
51+
flash('Post Updated')
52+
return redirect(url_for('blog_posts.blog_post', blog_post_id=blog_post.id))
53+
# Pass back the old blog post information so they can start again with
54+
# the old text and title.
55+
elif request.method == 'GET':
56+
form.title.data = blog_post.title
57+
form.text.data = blog_post.text
58+
return render_template('create_post.html', title='Update',
59+
form=form)
60+
61+
62+
@blog_posts.route("/<int:blog_post_id>/delete", methods=['POST'])
63+
@login_required
64+
def delete_post(blog_post_id):
65+
blog_post = BlogPost.query.get_or_404(blog_post_id)
66+
if blog_post.author != current_user:
67+
abort(403)
68+
db.session.delete(blog_post)
69+
db.session.commit()
70+
flash('Post has been deleted')
71+
return redirect(url_for('core.index'))

puppycompanyblog/core/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

puppycompanyblog/core/views.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from flask import render_template,request,Blueprint
2+
from puppycompanyblog.models import BlogPost
3+
4+
core = Blueprint('core',__name__)
5+
6+
@core.route('/')
7+
def index():
8+
'''
9+
This is the home page view. Notice how it uses pagination to show a limited
10+
number of posts by limiting its query size and then calling paginate.
11+
'''
12+
page = request.args.get('page', 1, type=int)
13+
blog_posts = BlogPost.query.order_by(BlogPost.date.desc()).paginate(page=page, per_page=10)
14+
return render_template('index.html',blog_posts=blog_posts)
15+
16+
@core.route('/info')
17+
def info():
18+
'''
19+
Example view of any other "core" page. Such as a info page, about page,
20+
contact page. Any page that doesn't really sync with one of the models.
21+
'''
22+
return render_template('info.html')

puppycompanyblog/data.sqlite

28 KB
Binary file not shown.
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from flask import Blueprint,render_template
2+
3+
error_pages = Blueprint('error_pages',__name__)
4+
5+
@error_pages.app_errorhandler(404)
6+
def error_404(error):
7+
'''
8+
Error for pages not found.
9+
'''
10+
# Notice how we return a tuple!
11+
return render_template('error_pages/404.html'), 404
12+
13+
@error_pages.app_errorhandler(403)
14+
def error_403(error):
15+
'''
16+
Error for trying to access something which is forbidden.
17+
Such as trying to update someone else's blog post.
18+
'''
19+
# Notice how we return a tuple!
20+
return render_template('error_pages/403.html'), 403

puppycompanyblog/models.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from puppycompanyblog import db,login_manager
2+
from datetime import datetime
3+
from werkzeug.security import generate_password_hash,check_password_hash
4+
from flask_login import UserMixin
5+
# By inheriting the UserMixin we get access to a lot of built-in attributes
6+
# which we will be able to call in our views!
7+
# is_authenticated()
8+
# is_active()
9+
# is_anonymous()
10+
# get_id()
11+
12+
13+
# The user_loader decorator allows flask-login to load the current user
14+
# and grab their id.
15+
16+
@login_manager.user_loader
17+
def load_user(user_id):
18+
return User.query.get(user_id)
19+
20+
class User(db.Model, UserMixin):
21+
22+
# Create a table in the db
23+
__tablename__ = 'users'
24+
25+
id = db.Column(db.Integer, primary_key = True)
26+
profile_image = db.Column(db.String(20), nullable=False, default='default_profile.png')
27+
email = db.Column(db.String(64), unique=True, index=True)
28+
username = db.Column(db.String(64), unique=True, index=True)
29+
password_hash = db.Column(db.String(128))
30+
# This connects BlogPosts to a User Author.
31+
posts = db.relationship('BlogPost', backref='author', lazy=True)
32+
33+
def __init__(self, email, username, password):
34+
self.email = email
35+
self.username = username
36+
self.password_hash = generate_password_hash(password)
37+
38+
def check_password(self,password):
39+
# https://stackoverflow.com/questions/23432478/flask-generate-password-hash-not-constant-output
40+
return check_password_hash(self.password_hash,password)
41+
42+
def __repr__(self):
43+
return f"UserName: {self.username}"
44+
45+
class BlogPost(db.Model):
46+
# Setup the relationship to the User table
47+
users = db.relationship(User)
48+
49+
# Model for the Blog Posts on Website
50+
id = db.Column(db.Integer, primary_key=True)
51+
# Notice how we connect the BlogPost to a particular author
52+
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
53+
date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
54+
title = db.Column(db.String(140), nullable=False)
55+
text = db.Column(db.Text, nullable=False)
56+
57+
def __init__(self, title, text, user_id):
58+
self.title = title
59+
self.text = text
60+
self.user_id =user_id
61+
62+
63+
def __repr__(self):
64+
return f"Post Id: {self.id} --- Date: {self.date} --- Title: {self.title}"

puppycompanyblog/static/master.css

Whitespace-only changes.

puppycompanyblog/static/master.js

Whitespace-only changes.
Loading
9.26 KB
Loading
9.26 KB
Loading
+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{% extends "base.html" %}
2+
{% block content %}
3+
4+
<div class="jumbotron">
5+
<div align='center'>
6+
<h1 >Welcome to the page for {{current_user.username}}</h1>
7+
<img align='center' src="{{ url_for('static', filename='profile_pics/' + current_user.profile_image) }}">
8+
<p>{{ current_user.email }}</p>
9+
</div>
10+
</div>
11+
<div class="container">
12+
<form method="POST" action = "" enctype="multipart/form-data">
13+
{{ form.hidden_tag() }}
14+
<div class="form-group">
15+
{{ form.username.label(class="form-group") }}
16+
{{form.username(class='form-control') }}
17+
</div
18+
<div class="form-group">
19+
{{ form.email.label(class="form-group") }}
20+
{{form.email(class='form-control') }}
21+
</div>
22+
<div class="form-group">
23+
{{ form.picture.label(class="form-group") }}
24+
{{ form.picture(class="form-control-file") }}
25+
</div>
26+
<div class="form-group">
27+
{{ form.submit(class="btn btn-primary") }}
28+
</div>
29+
</form>
30+
31+
</div>
32+
33+
{% endblock content %}

puppycompanyblog/templates/base.html

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
5+
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
6+
<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>
7+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
8+
<meta charset="utf-8">
9+
<title></title>
10+
</head>
11+
<body>
12+
<ul class="nav">
13+
14+
<li class="nav-item">
15+
<a class="nav-link" href="{{ url_for('core.index') }}">Home</a>
16+
</li>
17+
<li class="nav-item">
18+
<a class="nav-link" href="{{ url_for('core.info') }}">About Us</a>
19+
</li>
20+
{% if current_user.is_authenticated %}
21+
<li class="nav-link"><a href="{{ url_for('users.logout') }}">Log Out</a></li>
22+
<li class="nav-link"><a href="{{ url_for('users.account') }}">Account</a></li>
23+
<li class="nav-link"><a href="{{ url_for('blog_posts.create_post') }}">Create Post</a></li>
24+
{% else %}
25+
<li class="nav-link"><a href="{{ url_for('users.login') }}">Log In</a></li>
26+
<li class="nav-link"><a href="{{ url_for('users.register') }}">Register</a></li>
27+
{% endif %}
28+
29+
</ul>
30+
<div class="container">
31+
{% block content %}
32+
33+
{% endblock %}
34+
</div>
35+
36+
37+
</body>
38+
</html>
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{% extends "base.html" %}
2+
{% block content %}
3+
<div class="jumbotron">
4+
<h1>{{ post.title }}</h1>
5+
<h2>Written by: {{post.author.username}}</h2>
6+
<h3>Published: {{ post.date.strftime('%B %d, %Y') }}</h3>
7+
<p>{{post.text}}</p>
8+
{% if post.author == current_user %}
9+
<div>
10+
<a class="btn btn-secondary" href="{{ url_for('blog_posts.update', blog_post_id=post.id) }}">Update</a>
11+
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#del_modal">Delete</button>
12+
</div>
13+
{% endif %}
14+
</div>
15+
16+
17+
18+
19+
<!-- Modal for Pop Up-->
20+
{# https://getbootstrap.com/docs/4.1/components/modal/ #}
21+
{# Notice how the link with the id to the button above! #}
22+
<div class="modal" tabindex="-1" role="dialog" id="del_modal">
23+
<div class="modal-dialog" role="document">
24+
<div class="modal-content">
25+
<div class="modal-header">
26+
<h5 class="modal-title">Delete Post Pop up Modal</h5>
27+
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
28+
<span aria-hidden="true">&times;</span>
29+
</button>
30+
</div>
31+
<div class="modal-body">
32+
<p>Are you sure you want to delete this blog post?</p>
33+
</div>
34+
<div class="modal-footer">
35+
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
36+
37+
<form action="{{ url_for('blog_posts.delete_post', blog_post_id=post.id) }}" method="POST">
38+
<input class="btn btn-danger" type="submit" value="Delete">
39+
</form>
40+
</div>
41+
</div>
42+
</div>
43+
</div>
44+
45+
{% endblock content %}

0 commit comments

Comments
 (0)