article

Friday, April 2, 2021

Python Flask Blog with Admin using flask_sqlalchemy, flask_login, flask_bcrypt and flask_msearch

Python Flask Blog with Admin using flask_sqlalchemy, flask_login, flask_bcrypt and flask_msearch

-- Table: user
CREATE TABLE user (
    id       INTEGER       NOT NULL,
    name     VARCHAR (80)  NOT NULL,
    username VARCHAR (80)  NOT NULL,
    email    VARCHAR (120) NOT NULL,
    password VARCHAR (200) NOT NULL,
    profile  VARCHAR (180),
    PRIMARY KEY (
        id
    ),
    UNIQUE (
        username
    ),
    UNIQUE (
        email
    )
);

INSERT INTO user (id, name, username, email, password, profile) VALUES (3, 'admin', 'admin', 'cairocoders@gmail.com', X'243262243132244A4A757A326B696734342E6835706B4A43303533382E6D315A53687369393631505278526A4D5767373337746939526372314C3761', 'profile.jpg');

-- Table: post
CREATE TABLE post (
    id       INTEGER       NOT NULL,
    title    VARCHAR (200) NOT NULL,
    slug     VARCHAR (200) NOT NULL,
    body     TEXT          NOT NULL,
    category VARCHAR (100) NOT NULL,
    image    VARCHAR (150) NOT NULL,
    user_id  INTEGER       NOT NULL,
    views    INTEGER,
    comments INTEGER,
    feature  VARCHAR       NOT NULL,
    date_pub DATETIME      NOT NULL,
    PRIMARY KEY (
        id
    ),
    UNIQUE (
        title
    ),
    UNIQUE (
        slug
    ),
    FOREIGN KEY (
        user_id
    )
    REFERENCES user (id) ON DELETE CASCADE
);

-- Table: comments
CREATE TABLE comments (
    id       INTEGER       NOT NULL,
    name     VARCHAR (200) NOT NULL,
    email    VARCHAR (200) NOT NULL,
    message  TEXT          NOT NULL,
    post_id  INTEGER       NOT NULL,
    feature  BOOLEAN       NOT NULL,
    date_pub DATETIME      NOT NULL,
    PRIMARY KEY (
        id
    ),
    FOREIGN KEY (
        post_id
    )
    REFERENCES post (id) ON DELETE CASCADE,
    CHECK (feature IN (0, 1) ) 
);

URL
http://127.0.0.1:5000/login
Username : admin
password : cairocoders

http://127.0.0.1:5000/signup
http://127.0.0.1:5000/admin
http://127.0.0.1:5000/addpost
http://127.0.0.1:5000/update/3
http://127.0.0.1:5000/comments/
http://127.0.0.1:5000/search?q=python
http://127.0.0.1:5000/news/7-essential-vs-code-extensions-for-python-developers-in-2021

pip install Flask-Bcrypt = https://pypi.org/project/Flask-Bcrypt/
pip install Flask-Login = https://pypi.org/project/Flask-Login/
pip install flask-msearch = https://pypi.org/project/flask-msearch/
pip install Flask-SQLAlchemy = https://pypi.org/project/Flask-SQLAlchemy/


templates : https://colorlib.com/wp/template/force/
app.py
 
#app.py
from flaskblog import app

if __name__ == "__main__":
    app.run(debug=True)
flaskblog/__init__.py
 
#flaskblog/__init__.py
from flask import Flask
from flask_bcrypt import Bcrypt #pip install Flask-Bcrypt https://pypi.org/project/Flask-Bcrypt/
from flask_sqlalchemy import SQLAlchemy  #pip install Flask-SQLAlchemy = https://pypi.org/project/Flask-SQLAlchemy/
from flask_login import LoginManager #pip install Flask-Login = https://pypi.org/project/Flask-Login/
from flask_msearch import Search #pip install flask-msearch = https://pypi.org/project/flask-msearch/

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db'
app.config['SECRET_KEY']='cairocoders-ednalan'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=True


db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
search = Search()
search.init_app(app)

login_manager = LoginManager(app)

login_manager.login_view = "login"
login_manager.login_message_category = "info"

from flaskblog import routes
flaskblog/routes.py
 
#flaskblog/routes.py
from flask import  render_template, redirect, url_for, request, flash,current_app,abort
from flask_login import login_user, login_required,logout_user,current_user
from flaskblog import app, bcrypt,db,search
from .forms import SignUpForm,LoginForm,PostForm
from .models import User, Post,Comments
import os 
import secrets


def save_photo(photo):
    rand_hex  = secrets.token_hex(10)
    _, file_extention = os.path.splitext(photo.filename)
    file_name = rand_hex + file_extention
    file_path = os.path.join(current_app.root_path, 'static/images', file_name)
    photo.save(file_path)
    return file_name


@app.route('/')
def index():
    posts = Post.query.order_by(Post.id.desc()).all()
    return render_template('post/index.html', posts=posts)


@app.route('/news/<string:slug>', methods=['POST','GET'])
def news(slug):
    post = Post.query.filter_by(slug=slug).first()
    comment = Comments.query.filter_by(post_id=post.id).filter_by(feature=True).all()
    post.views = post.views + 1
    db.session.commit()
    Thanks =""
    if request.method =="POST":
        post_id = post.id
        name = request.form.get('name')
        email = request.form.get('email')
        message = request.form.get('message')
        comment = Comments(name=name,email=email,message=message,post_id=post_id)
        db.session.add(comment)
        post.comments = post.comments + 1
        db.session.commit()
        flash('Your comment has been submited  submitted will be published after aproval of admin', 'success')
        return redirect(request.url)

    return render_template('post/news-details.html', post=post, comment=comment, Thanks=Thanks)


@app.route('/search')
def search():
    keyword = request.args.get('q')
    posts = Post.query.msearch(keyword,fields=['title'],limit=6)
    return render_template('post/search.html', posts=posts)
    

@app.route('/admin')
@login_required
def admin():
    posts = Post.query.order_by(Post.id.desc()).all()
    return render_template('admin/home.html',posts = posts)


@app.route('/comments/', methods=['POST','GET'])
def comments():
    comments =Comments.query.order_by(Comments.id.desc()).all()
    return render_template('admin/comment.html',comments=comments)


@app.route('/check/<int:id>', methods=['POST','GET'])
@login_required
def check(id):
    comment = Comments.query.get_or_404(id)
    if (comment.feature == True):
        comment.feature = False
        db.session.commit()
    else:
        comment.feature = True
        db.session.commit()
        return redirect(url_for('comments')) 
    return redirect(url_for('comments'))


@app.route('/addpost',methods=['POST','GET'])
@login_required
def addpost():
    form = PostForm(request.form)
    if request.method =="POST" and form.validate():
        photo = save_photo(request.files.get('photo'))
        post = Post(title=form.title.data, body=form.content.data,category=request.form.get('category'),image=photo,author=current_user)
        db.session.add(post)
        db.session.commit()
        flash('Your post has been added ','success')
        return redirect(url_for('admin'))
    return render_template('admin/addpost.html', form=form)


@app.route('/update/<int:id>',methods=['POST','GET'])
@login_required
def update(id):
    form = PostForm(request.form)
    post = Post.query.get_or_404(id)
    form.title.data = post.title 
    form.content.data = post.body
    if request.method=='POST' and form.validate():
        if request.files.get('photo'):     
            try:
                os.unlink(os.path.join(current_app.root_path, 'static/images/'+ post.image))
                post.image = save_photo(request.files.get('photo'))
            except:
                post.image = save_photo(request.files.get('photo'))
        post.title = form.title.data
        post.body = form.content.data
        post.category = request.form.get('category')
        flash('Post has been updated', 'success')
        db.session.commit()
        return redirect(url_for('admin'))
    return render_template('admin/addpost.html', form=form, post=post)


@app.route('/delete/<int:id>')
@login_required
def delete(id):
    post = Post.query.get_or_404(id)
    try:
        os.unlink(os.path.join(current_app.root_path,'static/images/'+ post.image))
        db.session.delete(post)
    except:
        db.session.delete(post)
    flash('Post has deleted ','success')
    db.session.commit()
    return redirect(url_for('admin'))

@app.route('/delcomment/<int:id>')
@login_required
def delcomment(id):
    comment = Comments.query.get_or_404(id)
    db.session.delete(comment)
    db.session.commit()
    flash('Comment has deleted ','success')
    return redirect(url_for('admin'))


@app.route('/signup', methods=['POST','GET'])
def signup():
    form = SignUpForm(request.form)
    if request.method == 'POST' and form.validate():
        hashed_password = bcrypt.generate_password_hash(form.password.data)
        user = User(name=form.name.data,username=form.username.data, email=form.email.data,password=hashed_password)
        db.session.add(user)
        db.session.commit()
        flash('Thanks for registering, you able to login now','success')
        return redirect(url_for('login'))
    return render_template('admin/signup.html', form=form)



@app.route('/login', methods=['POST','GET'])
def login(): #username : admin pass: cairocoders
    if current_user.is_authenticated:
        next = request.args.get('next')
        return redirect(next or url_for('admin'))
    form = LoginForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User.query.filter_by(username=form.username.data).first()
        if not user:
            flash('This user not exists','warning')
            return redirect(url_for('login'))
        if user and bcrypt.check_password_hash(user.password, form.password.data):
            login_user(user)
            flash('Logged in successfully.','success')
            next = request.args.get('next')
            return redirect(next or url_for('admin'))
        flash('Invalid password','danger')
    return render_template('admin/login.html', form=form)


@app.route('/logout')
@login_required
def logout():
    logout_user()
    flash('you are logout','success')
    return redirect(url_for('login'))
flaskblog/models.py
 
#flaskblog/models.py
from flaskblog import db, login_manager
from datetime import datetime
from flask_login import UserMixin
from sqlalchemy import event
from slugify import slugify

@login_manager.user_loader
def load_user(user_id):
    return User.query.filter_by(id=user_id).first()

class User(db.Model,UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(200),  nullable=False)
    profile = db.Column(db.String(180),  default="profile.jpg")

    def __repr__(self):
        return '<User %r>' % self.username


class Post(db.Model):
    __searchable__ = ['title', 'body']
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), unique=True, nullable=False)
    slug = db.Column(db.String(200), unique=True, nullable=False)
    body = db.Column(db.Text, nullable=False)
    category = db.Column(db.String(100), nullable=False)
    image = db.Column(db.String(150), nullable=False, default='no-image.jpg')
    user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)
    author = db.relationship('User', backref=db.backref('posts',lazy=True, passive_deletes=True))
    views = db.Column(db.Integer,default=0)
    comments = db.Column(db.Integer,default=0)
    feature = db.Column(db.String, default=1, nullable=False)
    date_pub = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

    def __repr__(self):
        return '<Post %r' % self.title

    @staticmethod
    def generate_slug(target, value, oldvalue, initiator):
        if value and (not target.slug or value != oldvalue):
            target.slug = slugify(value)
db.event.listen(Post.title, 'set',Post.generate_slug, retval=False)


class Comments(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), unique=False, nullable=False)
    email = db.Column(db.String(200), unique=False, nullable=False)
    message = db.Column(db.Text, nullable=False)
    post_id = db.Column(db.Integer, db.ForeignKey('post.id', ondelete='CASCADE'), nullable=False)
    post = db.relationship('Post', backref=db.backref('posts',lazy=True, passive_deletes=True))
    feature = db.Column(db.Boolean, default=False, nullable=False)
    date_pub = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

    def __repr__(self):
        return '<Post %r' % self.name
flaskblog/forms.py
 
#flaskblog/forms.py
from wtforms import Form, BooleanField, StringField,FileField, PasswordField, validators, TextAreaField,ValidationError
from flask_wtf.file import FileField, FileAllowed, FileRequired

class SignUpForm(Form):
    name = StringField('Name', [validators.Length(min=2, max=25)])
    username = StringField('Username', [validators.Length(min=4, max=25)])
    email = StringField('Email Address', [validators.Length(min=6, max=35)])
    password = PasswordField('Password', [validators.DataRequired(),
        validators.EqualTo('confirm', message='Passwords must match')])
    confirm = PasswordField('Repeat Password')

class LoginForm(Form):
    username = StringField('Username', [validators.Length(min=4, max=25)])
    password = PasswordField('Password',[validators.DataRequired()])
     
class PostForm(Form):
    title = StringField('Title',[validators.DataRequired()])
    content = TextAreaField('Content', [validators.DataRequired()])
    photo = FileField()
flaskblog/templates/post/base.html
 
//flaskblog/templates/post/base.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="icon" href="{{ url_for('static', filename='img/favicon.png')}}" type="image/png">
    <title>Python Flask Blog with Admin using flask_sqlalchemy, flask_login, flask_bcrypt and flask_msearch</title>
    <link rel="stylesheet" href="{{url_for('static', filename='css/bootstrap.css')}}">
    <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">
    <link rel="stylesheet" href="{{url_for('static', filename='css/style.css')}}">
    <link rel="stylesheet" href="{{url_for('static', filename='css/responsive.css')}}">
</head>
<body>
    <header class="header_area"> 
        <div class="logo_part">
            <div class="container">
                <div class="float-left">
                    <a class="logo" href="#"><img src="{{ url_for('static', filename='images/logo.png')}}" alt=""></a>
                </div>
            </div>
        </div>
        <div class="main_menu">
            <nav class="navbar navbar-expand-lg navbar-light">
                <div class="container">
                    <div class="container_inner">
                        <div class="collapse navbar-collapse offset" id="navbarSupportedContent">
                            <ul class="nav navbar-nav menu_nav">
                                <li class="nav-item active"><a class="nav-link" href="/">Home</a></li>
                                <li class="nav-item"><a class="nav-link" href="#">About</a></li>
                                <li class="nav-item"><a class="nav-link" href="#">Contact</a></li>
                            </ul>
                            <ul class="nav navbar-nav navbar-right ml-auto">
                                <form action="{{ url_for('search')}}" method="GET" class="input-group mr-2">
                                    <input type="text" class="form-control" name="q">
                                    <div class="input-group-append">
                                        <input type="submit" class="btn btn-outline-info" value="Search">
                                    </div>
                                </form>
                            </ul>
                        </div>
                    </div>
                </div>
            </nav>
        </div>
    </header>
    {% block content  %}

    {% endblock content %}
    <footer class="footer-area">
        <div class="container">
            <p>
                Copyright ©<script>
                    document.write(new Date().getFullYear());
                </script> All rights reserved 
            </p>
        </div>
        </div>
    </footer>
    <script src="{{url_for('static', filename='js/jquery-3.2.1.min.js')}}"></script>
    <script src="{{url_for('static', filename='js/popper.js')}}"></script>
    <script src="{{url_for('static', filename='js/bootstrap.min.js')}}"></script>
</body>
</html>
flaskblog/templates/post/index.html
#flaskblog/templates/post/index.html
    {% extends 'post/base.html' %}
    {% block content  %}
    {% include 'post/slider.html'%}
    <br>
    <section class="news_area">
    	<div class="container">
    		<div class="row">
    			<div class="col-lg-8">
    				<div class="main_title2">
    					<h2>Latest Blog</h2>
    				</div>
    				<div class="latest_news">
    					{% for post in posts %}
    					<div class="media">
    						<div class="d-flex">
    							<img class="img-fluid" src="{{url_for('static',filename='images/' + post.image)}}"
    								style="width:200px; height:200px;">
    						</div>
    						<div class="media-body">
    							<div class="choice_text">
    								<div class="date">
    									<a class="" href="#">{{post.author.name}}</a>
    									<a href="#"><i class="fa fa-calendar" aria-hidden="true"></i>March 14, 2018</a>
    									<a href="#"><i class="fa fa-comments-o" aria-hidden="true"></i> Views
    										{{post.views }} | {{post.comments}} Comennts</a>
    								</div>
    								<a href="{{url_for('news', slug=post.slug)}}">
    									<h4>{{post.title}}</h4>
    								</a>
    								<p>{{post.body |truncate(200, True) }}</p>
    							</div>
    						</div>
    					</div>
    					{% endfor %}
    				</div>
    			</div>
    		</div>
    	</div>
    </section>
    {% endblock content  %}
flaskblog/templates/post/news-details.html
#flaskblog/templates/post/news-details.html
{% extends 'post/base.html' %}
{% block content %}
        
        <section class="news_area single-post-area p_100">
        	<div class="container">
        		<div class="row">
        			<div class="col-lg-8">
       					<div class="main_blog_details">
       						<img class="img-fluid" src="{{url_for('static',filename='images/' + post.image)}}" alt="" style="width:100%; height: 400px;">
       						<h4> {{post.title}} </h4>
       						<div class="user_details">
       							<div class="float-left">
       								<a href="#">Python</a>
       								<a href="#">Flask</a>
       							</div>
       							<div class="float-right">
       								<div class="media">
       									<div class="media-body">
       										<h5>Cairocoder</h5>
       										<p>12 Dec, 2020 11:21 am</p>
       									</div>	
       								</div>
       							</div>
       						</div>
       						<p>{{post.body}}</p>
						
      						<div class="news_d_footer">
      							<a href="#"><i class="lnr lnr lnr-heart"></i>cairocoders and 4 people like this</a>
      							<a class="justify-content-center ml-auto" href="#"><i class="lnr lnr lnr-bubble"></i> {{post.views }} Views  | {{post.comments}} Comments</a>
      							<div class="news_socail ml-auto">
									<a href="#"><i class="fa fa-facebook"></i></a>
									<a href="#"><i class="fa fa-twitter"></i></a>
									<a href="#"><i class="fa fa-youtube-play"></i></a>
									<a href="#"><i class="fa fa-pinterest"></i></a>
									<a href="#"><i class="fa fa-rss"></i></a>
								</div>
      						</div>
       					</div>
                        <div class="comments-area">
							<h4>{{post.comments}} Comments</h4>
							{% for message in comment %}
                            <div class="comment-list">
                                <div class="single-comment justify-content-between d-flex">
                                    <div class="user justify-content-between d-flex">
                                        <div class="thumb">
                                            <img src="img/blog/c1.jpg" alt="">
										</div>
										
                                        <div class="desc">
                                            <h5><a href="#">{{message.name}}</a></h5>
                                            <p class="date">{{message.date_pub.strftime('%Y')}}</p>
                                            <p class="comment">
                                                {{message.message}}
                                            </p>
										</div>
										
                                    </div>
                                    <div class="reply-btn">
                                           <!-- <a href="" class="btn-reply text-uppercase">reply</a>  -->
                                    </div>
                                </div>
                            </div>	
							{% endfor %}
                    			                                             				
                        </div>
                        <div class="comment-form">
							{% include 'admin/messages.html' %}
                            <h4>Leave a Comment</h4>
                            <form method="POST">
                                <div class="form-group form-inline">
                                  <div class="form-group col-lg-6 col-md-6 name">
                                    <input type="text" name="name" class="form-control" id="name" placeholder="Enter Name" onfocus="this.placeholder = ''" onblur="this.placeholder = 'Enter Name'">
                                  </div>
                                  <div class="form-group col-lg-6 col-md-6 email">
                                    <input type="email" name="email" class="form-control" id="email" placeholder="Enter email address" onfocus="this.placeholder = ''" onblur="this.placeholder = 'Enter email address'">
                                  </div>										
                                </div>
                                <div class="form-group">
                                    <textarea name="message" class="form-control mb-10" rows="5" name="message" placeholder="Messege" onfocus="this.placeholder = ''" onblur="this.placeholder = 'Messege'" required=""></textarea>
                                </div>
                                <button type="submit" class="primary-btn submit_btn">Post Comment</button>	
                            </form>
                        </div>
        			</div>
        			<div class="col-lg-4">
        				Right Side
        			</div>
        		</div>
        	</div>
        </section>	
       {% endblock content %}
flaskblog/post/search.html
//flaskblog/post/search.html
{% extends 'post/base.html' %}
{% block content %}

<section class="news_area single-post-area p_100">
	<div class="container">
		<div class="row">
			<div class="col-lg-8">
				<div class="main_blog_details">
					<div class="latest_news">
						{% for post in posts %}
						<div class="media">
							<div class="d-flex">
								<img class="img-fluid" src="{{url_for('static',filename='images/' + post.image)}}"
									style="width:200px; height:200px;">
							</div>
							<div class="media-body">
								<div class="choice_text">
									<div class="date">
										<a class="" href="#">{{post.author.name}}</a>
										<a href="#"><i class="fa fa-calendar" aria-hidden="true"></i>March 14, 2020</a>
										<a href="#"><i class="fa fa-comments-o" aria-hidden="true"></i> Views
											{{post.views }} | {{post.comments}} Comennts</a>
									</div>
									<a href="{{url_for('news', slug=post.slug)}}">
										<h4>{{post.title}}</h4>
									</a>
									<p>{{post.body |truncate(200, True) }}</p>
								</div>
							</div>
						</div>
						{% endfor %}

					</div>
				</div>
			</div>
			<div class="col-lg-4">
				Right 
			</div>
		</div>
	</div>
</section>
{% endblock content %}
flaskblog/templates/post/slider.html
//flaskblog/templates/post/slider.html
 <section class="home_banner_area">
        <div class="banner_inner d-flex align-items-center bg-light">
            <div class="container">
                <div id="carouselExampleIndicators" class="carousel slide" data-ride="carousel">
                    <ol class="carousel-indicators" style="position:absolute; bottom:0%;">
                        <li data-target="#carouselExampleIndicators" data-slide-to="0" class="active"></li>
                        <li data-target="#carouselExampleIndicators" data-slide-to="1"></li>
                        <li data-target="#carouselExampleIndicators" data-slide-to="2"></li>
                        <li data-target="#carouselExampleIndicators" data-slide-to="3"></li>
                    </ol>
                    <div class="carousel-inner">
                        
                        {% for post in posts %}
            
                        {% if loop.index == 1 %}
                        <div class="carousel-item active">
                            {% else %}
                        <div class="carousel-item">
                                {% endif %}
                              
                            <div class="background_image banner_inner" style="background-image:url('{{url_for('static', filename='images/'+ post.image)}}');">
                                <div class="banner_content text-center" style="position:absolute; right:25%; top: 50%;">
                                        <div class="date">
                                            <a href="#">Gadgets</a>
                                            <a href="#"><i class="fa fa-calendar" aria-hidden="true"></i>{{post.date_pub.strftime('%B %Y %d')}}</a>
                                            <a href="#"><i class="fa fa-comments-o" aria-hidden="true"></i>05</a>
                                        </div>
                                        <h4><a href="{{url_for('news', slug=post.slug)}}"  class="text-white" style="text-shadow:5px 5px 10px black;">{{post.title}}</a> </h4>
                                        <p style="text-shadow:5px 5px 10px black;">{{post.body| truncate(150, True)}}</p>
                                    </div>
                            </div>
                           
                        </div>
                        {% endfor %}
                    </div>
                </div>
            </div>
        </div>
    </section>  
flaskblog/templates/admin/login.html
//flaskblog/templates/admin/login.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="icon" href="static/web.jpg">
    <title> Login</title>
  </head>
  <body>
    <h1 class="text-center">  Login now </h1>
    {% from "admin/_formhelpers.html" import render_field %}
 <div class="container">
    <div class="row">
        <div class="col-md-3"></div>
        <div class="col-md-6">
          {% include 'admin/messages.html' %}
                <form method="post">
                        <div>
                          {{ render_field(form.username, class="form-control") }} 
                          {{ render_field(form.password, class="form-control") }}
                        </div>
                        <p><input type=submit value="Login" class="btn btn-info">
                      </form>
        </div>
        <div class="col-md-3"></div>
    </div>
 </div>
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>
flaskblog/templates/admin/signup.html
//flaskblog/templates/admin/signup.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <link rel="icon" href="static/web.jpg">
    <title> Sign Up</title>
  </head>
  <body>
    <h1 class="text-center"> Sign up now </h1>
    {% from "admin/_formhelpers.html" import render_field %}
 <div class="container">
    <div class="row">
        <div class="col-md-3"></div>
        <div class="col-md-6">
          {% include 'admin/messages.html' %}
                <form method="post">
                        <div>
                          {{ render_field(form.name, class="form-control")}}
                          {{ render_field(form.username, class="form-control") }}
                          {{ render_field(form.email, class="form-control") }}
                          {{ render_field(form.password, class="form-control") }}
                          {{ render_field(form.confirm, class="form-control") }}
                        </div>
                        <p><input type=submit value="Register" class="btn btn-info">
                      </form>
        </div>
        <div class="col-md-3"></div>
    </div>
 </div>
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>
flaskblog/templates/admin/home.html
//flaskblog/templates/admin/home.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <title> Dashboard </title>
  </head>
  <body>
    <h2 class="text-center">User  Dashboard </h2>
    {% include 'admin/messages.html' %}
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
      <a class="navbar-brand" href="{{url_for('admin')}}">Navbar</a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
      <div class="collapse navbar-collapse" id="navbarSupportedContent">
        <ul class="navbar-nav mr-auto">
          <li class="nav-item active">
            <a class="nav-link" href="{{url_for('admin')}}">Home <span class="sr-only">(current)</span></a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{{url_for('addpost')}}">Add post</a>
          </li>
          <li class="nav-item">
              <a class="nav-link" href="{{url_for('comments')}}">Comments</a>
            </li>
        </ul>
        <ul class="navbar-nav">
          <li class="nav-item">
            <a class="nav-link" href="{{ url_for('logout')}}"aria-disabled="true">Logout</a>
          </li>
        </ul>
      </div>
    </nav>

    <table class="table table-striped table-bordered table-sm">
      <thead class="bg-dark text-white">
        <th>Sr</th>
        <th>Title</th>
        <th>Category</th>
        <th>Author</th>
        <th>Date</th>
        <th>Image</th>
        <th>Edit</th>
        <th>Delete</th>
      </thead>
       <tbody>
         {% for post in posts %}
         <tr>
           <td>{{loop.index}}</td>
           <td>{{post.title}}</td>
           <td>{{post.category}}</td>
           <td>{{post.author.name}}</td>
           <td>{{post.date_pub.strftime('%Y %B %d')}}</td>
           <td> <img src="{{url_for('static', filename='images/' + post.image)}}" alt="{{post.category}}" width="40"></td>
           <td> <a href="update/{{ post.id}}" class="btn btn-sm btn-info">Edit</a> </td>
    
           <td><button type="button" class="btn btn-danger btn-sm" data-toggle="modal" data-target="#del{{post.id}}">
              delete
            </button></td>
         </tr>
  <!-- The Modal -->
  <div class="modal" id="del{{post.id}}">
    <div class="modal-dialog">
      <div class="modal-content">
        <!-- Modal Header -->
        <div class="modal-header">
          <h4 class="modal-title text-danger"> Are you do you want delete this post </h4>
          <button type="button" class="close" data-dismiss="modal">×</button>
        </div>
        <div class="modal-body">
         {{post.title}}
        </div>
        <div class="modal-footer">
         <div class="mr-auto"> <a href="delete/{{ post.id}}" class="btn btn-danger btn-sm"> Delete </a></div>
          <button type="button" class="btn btn-primary btn-sm" data-dismiss="modal">Cancel</button>
        </div>
      </div>
    </div>
  </div>
         {% endfor %}
       </tbody>
    </table>
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
  </body>
</html>
flaskblog/templates/admin/comment.html
//flaskblog/templates/admin/comment.html
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
    <script src="https://code.jquery.com/jquery-3.4.0.js">
    </script>
    <title> Dashboard-Comments </title>
</head>
<body>
    <h2 class="text-center">Dashboard-Comments </h2>
    {% include 'admin/messages.html' %}
    <nav class="navbar navbar-expand-lg navbar-light bg-light">
        <a class="navbar-brand" href="{{url_for('admin')}}">Navbar</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
            aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="{{url_for('admin')}}">Home <span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="{{url_for('addpost')}}">Add post</a>
                </li>
            </ul>
            <ul class="navbar-nav">
                <li class="nav-item">
                    <a class="nav-link" href="{{ url_for('logout')}}" aria-disabled="true">Logout</a>
                </li>
            </ul>
        </div>
    </nav>
    <table class="table table-striped table-bordered table-sm">
        <thead class="bg-dark text-white">
            <th>Sr</th>
            <th>Name</th>
            <th>Comments</th>
            <th>Post ID</th>
            <th>Date</th>
            <th>Status</th>
            <td>Delete</td>
        </thead>
        <tbody>
            {% for comment in comments %}
            <tr>
                <td>{{loop.index}}</td>
                <td>{{comment.name}}</td>
                <td>{{comment.message | truncate(100, True)}}</td>
                <td>{{comment.post.id}}</td>
                <!-- <td>{{comment.feature}}</td> -->
                <td>{{comment.date_pub.strftime('%Y %B %d')}}
                </td>
                {% if comment.feature == False %}
                <td>
                    <a href="{{ url_for('check', id=comment.id) }}" class="text-center text-danger"> Pandding </a>
                </td>
                {% else %}
                <td>
                    <a href="{{ url_for('delcomment', id=comment.id) }}" class="text-success"> Aproved </a>
                </td>
                {% endif %}
                <td>
                    <a href="{{ url_for('check', id=comment.id) }}" class="btn btn-sm btn-danger"> Delete </a>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js">
    </script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js">
    </script>
</body>
</html>
flaskblog/templates/admin/messages.html
//flaskblog/templates/admin/messages.html
{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <div class=flashes>
    {% for category, message in messages %}
      <div class=" alert alert-{{ category }}">{{ message }}</div>
    {% endfor %}
    </div>
  {% endif %}
{% endwith %}
flaskblog/templates/admin/_formhelpers.html
//flaskblog/templates/admin/_formhelpers.html
{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li class="text-danger">{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

Related Post