Skip to content
Snippets Groups Projects
Commit bfb99fb3 authored by Rongda Wang's avatar Rongda Wang
Browse files

Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 511 additions and 0 deletions
.idea
.DS_Store
\ No newline at end of file
Reference
Boostrap - all html pages used boostrap to polish the UI component.
fetch api - fetch api is used to do ajax call, since the api is more friendly than XMLHttpRequest
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
app = Flask(__name__)
app.config['SECRET_KEY'] = 'abcd'
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://c2063316:Wangji19970904@csmysql.cs.cf.ac.uk:3306/c2063316_db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
from blog import routes
File added
File added
File added
File added
SQLALCHEMY_TRACK_MODIFICATIONS = True
\ No newline at end of file
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, SelectField, BooleanField, DateField
from wtforms.widgets import TextArea
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError, Regexp
from blog.models import User
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(
'Please enter your username'), Length(min=3, max=15)])
email = StringField('Email',
validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(), Regexp('^.{6,8}$',
message='Your password should be between 6 and 8 characters long.')])
confirm_password = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password', message='Passwords must match')])
submit = SubmitField('Register')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError(
'Username already exist. Please choose a different one.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError(
'This email is already registered. Please choose a different one.')
class SearchForm(FlaskForm):
keyword = StringField(
'', validators=[DataRequired()])
submit = SubmitField('Search')
class LoginForm(FlaskForm):
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')
class CommentForm(FlaskForm):
content = StringField(
'Write a Comment', widget=TextArea(), validators=[DataRequired()])
submit = SubmitField('Comment')
from datetime import datetime
from blog import db, login_manager
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Text, nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, server_default=db.func.now())
image_url = db.Column(db.Text, nullable=False,
default='default.jpg')
author = db.Column(db.String(100), nullable=False,
default='Unknown')
hot = db.Column(db.Boolean, nullable=False,default=False)
def __repr__(self):
return f"Product('{self.title}')"
class Comment(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, server_default=db.func.now())
def __repr__(self):
return f"('{self.user_id}', '{self.post_id}', '{self.content}',, '{self.created_at}')"
class Tag(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('user.id'),primary_key=True)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), primary_key=True)
def __repr__(self):
return f"('{self.user_id}', '{self.post_id}')"
class Rating(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('user.id'),primary_key=True)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), primary_key=True)
def __repr__(self):
return f"('{self.user_id}', '{self.post_id}')"
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(15), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
password = db.Column(db.String(60), nullable=False)
def __repr__(self):
return f"User('{self.username}', '{self.email}')"
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
class Extra(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(40), nullable=False)
content = db.Column(db.Text, nullable=False)
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
import os
from flask import render_template, url_for, request, redirect, flash, session, jsonify, Response
from blog import app, db
from datetime import date, timedelta, datetime
from blog.models import Post, User, Comment, Rating, Tag, Extra
from blog.forms import RegistrationForm, LoginForm, CommentForm, SearchForm
from flask_login import login_user, current_user, logout_user, login_required
from sqlalchemy import or_, desc, asc, exc, exists, func
@app.route("/")
@app.route("/home", methods=['GET', 'POST'])
def home():
form = SearchForm()
posts = Post.query.filter(func.DATE(Post.created_at) >= (
datetime.now()-timedelta(days=30))).order_by(desc(Post.created_at)).limit(6).all()
hotTopics = Post.query.filter(Post.hot == True).limit(3).all()
return render_template('home.html', posts=posts, topics=hotTopics, title='home', form=form)
@app.route("/posts/<int:post_id>", methods=['GET', 'POST'])
def post(post_id):
form = CommentForm()
print(form.validate_on_submit())
if form.validate_on_submit():
if current_user.is_authenticated:
comment = Comment(user_id=current_user.id, post_id=post_id,
content=form.content.data)
db.session.add(comment)
db.session.commit()
else:
return redirect('/login')
post = Post.query.get_or_404(post_id)
comments = Comment.query.filter_by(post_id=post_id).join(
User, User.id == Comment.user_id).add_columns(User.id, User.username).order_by(desc(Comment.created_at)).all()
liked = Rating.query.filter_by(post_id=post_id).count()
tagged = []
if current_user.is_authenticated:
tagged = db.session.query(Tag.query.filter_by(
user_id=current_user.id, post_id=post_id).exists()).scalar()
form.content.data = ""
return render_template('post.html', post=post, comments=comments, liked=liked, tagged=tagged, form=form)
@app.route("/posts", methods=['GET'])
def posts():
order = request.args.get('order')
orderby = asc(Post.created_at) if order == 'asc' else desc(Post.created_at)
posts = Post.query.order_by(orderby).all()
return render_template('posts.html', posts=posts, title="All Posts")
@app.route("/posts/tagged", methods=['GET'])
def tagged():
if current_user.is_authenticated:
posts = Tag.query.filter_by(user_id=current_user.id).join(Post, Post.id == Tag.post_id).add_columns(
Post.id, Post.title, Post.content, Post.created_at, Post.image_url).order_by(desc(Post.created_at)).all()
print(posts)
return render_template('posts.html', posts=posts, title="Tagged Posts")
else:
return redirect('/login')
@app.route("/posts/tag/<int:post_id>", methods=['GET'])
def tag(post_id):
if current_user.is_authenticated:
try:
tag = Tag(user_id=current_user.id, post_id=post_id)
db.session.add(tag)
db.session.commit()
except exc.SQLAlchemyError:
return jsonify({"message": "You have tagged this post"}), 400
return jsonify({"message": "Tagged successfully"}), 200
else:
return jsonify({"message": "Please log in"}), 401
@app.route("/posts/untag/<int:post_id>", methods=['GET'])
def untag(post_id):
if current_user.is_authenticated:
try:
tag = Tag.query.filter_by(
user_id=current_user.id, post_id=post_id).one()
db.session.delete(tag)
db.session.commit()
except exc.SQLAlchemyError:
return jsonify({"message": "You have't tagged this post"}), 400
return jsonify({"message": "Untagged successfully"}), 200
else:
return jsonify({"message": "Please log in"}), 401
@app.route("/register", methods=['GET', 'POST'])
def register():
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data,
email=form.email.data, password=form.password.data)
db.session.add(user)
db.session.commit()
flash('Your account has been created. You can now log in.')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)
@app.route("/login", methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is not None and user.verify_password(form.password.data):
login_user(user)
flash('You are now logged in.')
return redirect(url_for('home'))
flash('Invalid username or password.')
return render_template('login.html', form=form)
return render_template('login.html', title='Login', form=form)
@app.route("/posts/like/<int:post_id>")
def like(post_id):
if current_user.is_authenticated:
like = Rating(user_id=current_user.id, post_id=post_id)
db.session.add(like)
try:
db.session.commit()
except exc.SQLAlchemyError:
return jsonify({"message": "You have liked the post"}), 400
return jsonify({"message": "liked successfully"}), 200
else:
return jsonify({"message": "Please log in"}), 401
@app.route("/logout")
def logout():
logout_user()
return redirect(url_for('home'))
@app.route("/disclaimer")
def disclaimer():
data = Extra.query.filter_by(title='Disclaimer').first()
return render_template('extra.html', data=data)
@app.route("/policy")
def policy():
data = Extra.query.filter_by(title='Privacy Policy').first()
return render_template('extra.html', data=data)
@app.route("/search", methods=['POST'])
def search():
form = SearchForm()
posts = Post.query.filter(or_(Post.title.like('%' + form.keyword.data + '%'),
Post.content.like('%' + form.keyword.data + '%'))).order_by(Post.created_at).all()
return render_template('search.html', posts=posts, form=form, title="Search results")
blog/static/img/post1.jpeg

19 KiB

blog/static/img/post2.png

13.3 KiB

blog/static/img/post3.png

128 KiB

blog/static/img/post4.png

22.5 KiB

blog/static/img/post5.png

268 KiB

blog/static/img/post6.png

16.1 KiB

blog/static/img/post7.png

15.9 KiB

async function like (event, id) {
let response = await fetch('/posts/like/' + id)
if (response.ok) {
let data = (await response.json())
showMessage(data.message,'success')
document.getElementById('liked').textContent = parseInt(document.getElementById('liked').textContent) + 1
} else {
let data = (await response.json())
showMessage(data.message)
}
}
async function tag (event, id) {
let response = await fetch('/posts/tag/' + id)
if (response.ok) {
document.getElementById('tag').style.display = 'none'
document.getElementById('untag').style.display = 'inline'
let data = (await response.json())
showMessage(data.message,'success')
} else {
let data = (await response.json())
showMessage(data.message)
}
}
async function untag (event, id) {
let response = await fetch('/posts/untag/' + id)
if (response.ok) {
document.getElementById('tag').style.display = 'inline'
document.getElementById('untag').style.display = 'none'
let data = (await response.json())
showMessage(data.message,'success')
} else {
let data = (await response.json())
showMessage(data.message)
}
}
function showMessage (message, type = 'danger') {
document.getElementById('message').style.display = 'block';
document.getElementById('message').innerHTML = `<div class="alert alert-${type} message" role="alert">
${message}
</div>`
setTimeout(e => {
document.getElementById('message').style.display = 'none'
}, 5000)
}
async function removeItem (event, id, title) {
event.preventDefault();
let ok = confirm(`Untag post ${title} ?`)
if (!ok)
return;
let response = await fetch('/posts/untag/' + id)
if (response.ok) {
let node = document.getElementById(`post-${id}`)
node.parentNode.removeChild(node);
} else {
let data = (await response.json())
alert(data.message)
}
}
// boostrap for show tootips
// https://getbootstrap.com/docs/4.0/components/tooltips/
$(function() {
$('[data-toggle="tooltip"]').tooltip()
$('input[rel="txtTooltip"]').tooltip();
})
html,body {
padding: 0px;
margin: 0px;
height: 100%;
}
body {
padding: 0px;
margin: 0px;
font-family: Arial,Helvetica,sans-serif;
background-color: #eee;
height: 100%;
}
h1 {
padding-left: 20px;
font-family: Montserrat,sans-serif;
}
.loggin,.register{
display: block;
max-width: 600px;
margin: 0 auto;
background-color: white;
margin-top: 10%;
padding: 30px;
}
.loggin .btn,.register .btn{
width: 100%;
}
#header {
padding:10px 100px;
font-size:18px;
overflow:auto;
background-color: #ffffe6;
}
.flashes{
list-style-type: none;
color: #fff;
}
#message{
z-index: 1000;
}
#message ul{
list-style: none;
}
.card-img{
width: 100%;
}
.has-drop-cap:not(:focus):first-letter {
float: left;
font-size: 8.4em;
line-height: .68;
font-weight: 100;
margin: .05em .1em 0 0;
text-transform: uppercase;
font-style: normal;
}
p{
white-space: pre-line;
}
.container{
background-color: white;
padding: 20px;
}
.list-group-item{
border: none;
}
.card .card-text{
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.comments{
padding: 30px;
}
.remove{
position:absolute;
right: 10px;
top:10px;
}
.img-left{
height: 200px;
width: 200px;
float: left;
margin-right: 20px;
}
footer{
padding: 20px;
text-align: center;
background-color: firebrick;
color: white;
}
#main-content {
min-height: calc(100vh - 91px);
}
.footer {
height: 60px;
}
textarea{
height: 150px;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment