Started with the rea Minibase Interface

master
key 2 years ago
parent 0f0f148418
commit 37415e93e3

@ -0,0 +1,44 @@
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
from flask_mail import Mail
from minibase.config import Config
# (DATABASE) Definition
db = SQLAlchemy()
# (PASSWORD) Hashign Program to save paswords safely
bcrypt = Bcrypt()
# (LOGIN) Login manage plugin configuration
login_manager = LoginManager()
login_manager.login_view = 'users.login'
login_manager.login_message_category = 'info' #Boostrap Info Message
# (EMAIL AGENT) Definition
mail = Mail()
def create_minibase(config_class=Config):
# (FLASK) Main Flask Application
app = Flask(__name__)
app.config.from_object(Config)
# (DATABASE) Initialisation
db.init_app(app)
# (PASSWORD) Initialisation
bcrypt.init_app(app)
# (LOGIN) Initialisation
login_manager.init_app(app)
# (EMAIL AGENT) Initialisation
mail.init_app(app)
# (FLASK) Importing adn then registering blueprints
from minibase.users.routes import users
from minibase.posts.routes import posts
from minibase.main.routes import main
from minibase.errors.handlers import errors
app.register_blueprint(users)
app.register_blueprint(posts)
app.register_blueprint(main)
app.register_blueprint(errors)
# Retunr The ccreated app
return app

@ -0,0 +1,20 @@
import os
class themeMinibase():
image="rounded-circle account-img"
class Config:
# (FLASK) Sectret key wich will be used to secure some requests an connections
SECRET_KEY = os.environ.get('MINIBASE_SECRET_KEY')
# (SQLALCHEMY) COnfiguration
SQLALCHEMY_DATABASE_URI = os.environ.get('MINIBASE_SQLALCHEMY_DATABASE_URI')
# (MAIL AGENT) Configure mail Server to send EMails.
MAIL_SERVER = os.environ.get('MINIBASE_MAIL_SERVER')
MAIL_USERNAME = os.environ.get('MINIBASE_MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MINIBASE_MAIL_PASSWORD')
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True

@ -0,0 +1,16 @@
from flask import Blueprint, render_template
errors = Blueprint('errors', __name__)
@errors.app_errorhandler(404)
def error_404(error):
return render_template('errors/404.html'), 404
@errors.app_errorhandler(403)
def error_403(error):
return render_template('errors/403.html'), 403
@errors.app_errorhandler(500)
def error_500(error):
return render_template('errors/500.html'), 500

@ -0,0 +1,21 @@
from flask import render_template, request, Blueprint
from minibase.models import Post
# Declaring a blueprint
main = Blueprint('main', __name__)
#Redirect from / and also /home routes to the /
@main.route("/")
@main.route("/home")
def home():
# (PAGINATION) Defines the page number that we will start with
page = request.args.get('page', 1, type=int)
# (POSTS) Query posts usin SQLAlchemy
posts = Post.query.order_by(Post.date_posted.asc()).paginate(per_page=2)
# (HTML) Renders the template for templates/home.html
return render_template('home.html', posts=posts)
@main.route("/about")
def about():
return render_template('about.html', title='About')

@ -0,0 +1,46 @@
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from datetime import datetime
from minibase import db, login_manager
from flask_login import UserMixin
from flask import url_for, current_app
@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)
posts = db.relationship('Post', backref='author', lazy=True)
def get_reset_token(self, expires_sec=1800):
s = Serializer(current_app.config['SECRET_KEY'], expires_sec)
return s.dumps({'user_id': self.id}).decode('utf-8')
@staticmethod
def verify_reset_token(token):
s = Serializer(current_app.config['SECRET_KEY'])
try:
user_id = s.loads(token)['user_id']
except:
return none
return User.query.get(user_id)
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
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)
def __repr__(self):
return f"User('{self.title}', '{self.date_posted}')"

@ -0,0 +1,10 @@
from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired
class postForm(FlaskForm):
title = StringField('Title',
validators=[DataRequired()])
content = TextAreaField('Content',
validators=[DataRequired()])
submit = SubmitField('Post')

@ -0,0 +1,57 @@
from flask import (render_template, url_for, flash,
redirect, request, abort, Blueprint)
from flask_login import current_user, login_required
from minibase import db
from minibase.models import Post
from minibase.posts.forms import postForm
from minibase.config import themeMinibase
# Declaring a blueprint
posts = Blueprint('posts', __name__)
@posts.route("/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('main.home'))
return render_template('create_post.html', title='Create Post', legend='Create Post', form=form)
@posts.route("/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, theme=themeMinibase)
@posts.route("/post/<int:post_id>/update", methods=['GET', 'POST'])
@login_required
def post_update(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('posts.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', legend='Update Post', form=form)
@posts.route("/post/<int:post_id>/delete", methods=['POST'])
@login_required
def post_delete(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('main.home'))

Binary file not shown.

@ -0,0 +1,80 @@
body {
background: #fafafa;
color: #333333;
margin-top: 5rem;
}
h1, h2, h3, h4, h5, h6 {
color: #444444;
}
.bg-steel {
background-color: #191717
}
.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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@ -0,0 +1,5 @@
{% extends "layout.html" %}
{% block content %}
<h1>About Page</h1>
{% endblock content %}

@ -0,0 +1,60 @@
{% 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>
</br>
<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>
</br>
<div class="form-group">
{{ form.picture.label() }} </br>
{{ form.picture(class="form-control-file") }}</br>
{% if form.picture.errors %}
{% for error in form.picture.errors %}
<span class="text-danger">{{ error }}</span></br>
{% endfor %}
{% endif %}
</div>
</br>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}

@ -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 %}

@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<h1>Your dont have permission to do that (403)</h1>
<p>PLease Check your account and try again</p>
</div>
{% endblock content %}

@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<h1>Ooops :S Page Not Found (404)</h1>
<p>That page doens not exist. Please try another one</p>
</div>
{% endblock content %}

@ -0,0 +1,9 @@
{% extends "layout.html" %}
cat: templates/errors/500.html: No such file or directory
<div class="content-section">
<h1>Something Went Wrong (500)</h1>
<p>There Was an Error from our side please give us a moment and try again later</p>
</div>
{% endblock content %}

@ -0,0 +1,28 @@
{% extends "layout.html" %}
{% block content %}
{% for post in posts.items %}
<article class="media content-section">
<img class="rounded-circle article-img" src="{{ url_for('static', filename='pics/' + post.author.image_file) }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{{ url_for('users.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('posts.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('main.home', page=page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-info mb-4" href="{{ url_for('main.home', page=page_num) }}">{{ page_num }}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
{% endblock content %}

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
{% if title %}
<title> MiniBase - {{ title }} </title>
{% else %}
<title> MiniBase </title>
{% endif %}
</head>
<body>
<header class="site-header">
<nav class="navbar navbar-expand-sm navbar-dark bg-steel fixed-top">
<div class="container">
<a class="navbar-brand mr-4" href="{{ url_for('main.home') }}">MiniBase</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('main.home') }}">Home</a>
<a class="nav-item nav-link" href="{{ url_for('main.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('posts.new_post') }}">New Post</a>
<a class="nav-item nav-link" href="{{ url_for('users.logout') }}">Logout</a>
<a class="nav-item nav-link" href="{{ url_for('users.account') }}">Account</a>
{% else %}
<a class="nav-item nav-link" href="{{ url_for('users.login') }}">Login</a>
<a class="nav-item nav-link" href="{{ url_for('users.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>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
</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="{{ url_for('users.reset_request') }}">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('users.register') }}">Sign Up Now</a>
</small>
</div>
{% endblock content %}

@ -0,0 +1,40 @@
{% extends "layout.html" %}
{% block content %}
<article class="media content-section">
<img class="{{ theme.image }} article-img" src="{{ url_for('static', filename='pics/' + post.author.image_file) }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{{ url_for('users.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 m-1" href="{{ url_for('posts.post_update', post_id=post.id) }}">Update</a>
<button type="button" class="btn btn-danger btn-sm m-1" data-bs-toggle="modal" data-bs-target="#staticBackdrop">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="staticBackdrop" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Your last chance, are you Sure?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<form action="{{ url_for('posts.post_delete', post_id=post.id) }}" method="POST">
<input class="btn btn-danger" type="submit" value="Delete">
</div>
</div>
</div>
</div>
{% endblock content %}

@ -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.password_confirm.label(class="form-control-label") }}
{% if form.password_confirm.errors %}
{{ form.password_confirm(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password_confirm.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password_confirm(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('users.login') }}">Sign In</a>
</small>
</div>
{% endblock content %}

@ -0,0 +1,27 @@
{% 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">Reset Password</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>
</fieldset>
<div class="form-group">
{{ form.submit(class="btn btn-outline-info") }}
</div>
</form>
</div>
{% endblock content %}

@ -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">Reset Password</legend>
<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.password_confirm.label(class="form-control-label") }}
{% if form.password_confirm.errors %}
{{ form.password_confirm(class="form-control form-control-lg is-invalid") }}
<div class="invalid-feedback">
{% for error in form.password_confirm.errors %}
<span>{{ error }}</span>
{% endfor %}
</div>
{% else %}
{{ form.password_confirm(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 %}

@ -0,0 +1,29 @@
{% 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='pics/' + post.author.image_file) }}">
<div class="media-body">
<div class="article-metadata">
<a class="mr-2" href="{{ url_for('users.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('posts.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('users.user_posts', username=user.username, page=page_num) }}">{{ page_num }}</a>
{% else %}
<a class="btn btn-outline-info mb-4" href="{{ url_for('users.user_posts', username=user.username, page=page_num) }}">{{ page_num }}</a>
{% endif %}
{% else %}
...
{% endif %}
{% endfor %}
{% endblock content %}

@ -0,0 +1,74 @@
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flask_login import current_user
from minibase.models import User
class registrationForm(FlaskForm):
username = StringField('User Name',
validators=[DataRequired(),Length(min = 3, max = 20)])
email = StringField('Email',
validators=[DataRequired(),Email()])
password = PasswordField('Password',
validators=[DataRequired()])
password_confirm = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Sing 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 another one')
def validate_email(self, email):
email = User.query.filter_by(email=email.data).first()
if email:
raise ValidationError('That email is taken do you have an acocunt ?')
class loginForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(),Email()])
password = PasswordField('Password',
validators=[DataRequired()])
remember = BooleanField('Remember Me')
submit = SubmitField('Log In')
class updateAccountForm(FlaskForm):
username = StringField('User Name',
validators=[DataRequired(),Length(min = 3, 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 another one')
def validate_email(self, email):
if email.data != current_user.email:
email = User.query.filter_by(email=email.data).first()
if email:
raise ValidationError('That email is taken do you have an acocunt ?')
class requestResetForm(FlaskForm):
email = StringField('Email',
validators=[DataRequired(),Email()])
submit = SubmitField('Request Password Reset')
def validate_email(self, email):
email = User.query.filter_by(email=email.data).first()
if email is None:
raise ValidationError('There is no Account with this email your must register first.')
class resetPasswordForm(FlaskForm):
password = PasswordField('Password',
validators=[DataRequired()])
password_confirm = PasswordField('Confirm Password',
validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Reset Password')

@ -0,0 +1,106 @@
from flask import render_template, url_for, flash, redirect, request, Blueprint
from flask_login import login_user, current_user, logout_user, login_required
from minibase import db, bcrypt
from minibase.models import User, Post
from minibase.users.forms import registrationForm, loginForm, updateAccountForm, requestResetForm, resetPasswordForm
from minibase.posts.forms import postForm
from minibase.users.utils import save_picture, send_reset_email
# Declaring a blueprint
users = Blueprint('users', __name__)
#Route is the file that is going to be generated
@users.route("/register", methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.home'))
form = registrationForm()
if form.validate_on_submit():
hashed_pw = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user = User(username=form.username.data, email=form.email.data, password=hashed_pw)
db.session.add(user)
db.session.commit()
flash(f'Your account has benn created you can now log in!','success')
return redirect(url_for('users.login'))
return render_template('register.html', title='Register', form=form)
@users.route("/login", methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.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('main.home'))
else:
flash('Login unsuccessful. Please chek your Email and Password!','danger')
return render_template('login.html', title='Login', form=form)
@users.route("/logout")
def logout():
logout_user()
return redirect(url_for('main.home'))
@users.route("/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('users.account'))
elif request.method == 'GET':
form.username.data = current_user.username
form.email.data = current_user.email
image_file = url_for('static', filename='pics/'+ current_user.image_file)
return render_template('account.html', title='Account', image_file = image_file, form=form)
@users.route("/user/<string:username>")
def user_posts(username):
user = User.query.filter_by(username=username).first_or_404()
page = request.args.get('page', 1, type=int)
posts = Post.query.filter_by(author=user)\
.order_by(Post.date_posted.asc())\
.paginate(page=page, per_page=2)
return render_template('user_posts.html', posts=posts, user=user)
@users.route("/reset_password", methods=['GET', 'POST'])
def reset_request():
if current_user.is_authenticated:
return redirect(url_for('main.home'))
form = requestResetForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
send_reset_email(user)
flash('An Email has benn sent with instruction to reset your password', 'warning')
return render_template('reset_request.html', title='Reset Password', form=form)
@users.route("/reset_password/<token>", methods=['GET', 'POST'])
def reset_token(token):
if current_user.is_authenticated:
return redirect(url_for('main.home'))
user = User.verify_reset_token(token)
if user is None:
flash('That is an invalid or expired token', 'warning')
return redirect(url_for('users.reset_request'))
form = resetPasswordForm()
if form.validate_on_submit():
hashed_pw = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
user.password = hashed_pw
db.session.commit()
flash(f'Your password has benn udated','success')
return redirect(url_for('users.login'))
return render_template('reset_token.html', title='Reset Password', form=form)

@ -0,0 +1,28 @@
import os
import secrets
from PIL import Image
from flask import url_for, current_app
from flask_mail import Message
from minibase import mail
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
picture_path = os.path.join(current_app.root_path, 'static/pics', picture_fn)
output_size = (125,125)
i = Image.open(form_picture)
i.thumbnail(output_size)
i.save(picture_path)
return picture_fn
def send_reset_email(user):
token = user.get_reset_token()
msg = Message('Password Reset Request',
sender='noreply@demo.com',
recipients=[user.email])
msg.body = f'''To reset your password, visit the following link:
{url_for('reset_token', token=token, _external=True)}
If you didn't make this request, then simply ingnore this email and no chancges will be made.
'''
mail.send(msg)

@ -0,0 +1,7 @@
from minibase import create_minibase
#Enable debug option even if run directly form Python3
app=create_minibase()
if __name__ == '__main__':
app.run(debug=True)

Binary file not shown.

@ -13,7 +13,7 @@ bcrypt = Bcrypt()
# (LOGIN) Login manage plugin configuration # (LOGIN) Login manage plugin configuration
login_manager = LoginManager() login_manager = LoginManager()
login_manager.login_view = 'login' login_manager.login_view = 'users.login'
login_manager.login_message_category = 'info' #Boostrap Info Message login_manager.login_message_category = 'info' #Boostrap Info Message
# (EMAIL AGENT) Definition # (EMAIL AGENT) Definition
@ -32,11 +32,7 @@ def create_minibase(config_class=Config):
# (EMAIL AGENT) Initialisation # (EMAIL AGENT) Initialisation
mail.init_app(app) mail.init_app(app)
# (FLASK) Importing adn then registering blueprints # (FLASK) Importing adn then registering blueprints
from minibase.users.routes import users
from minibase.posts.routes import posts
from minibase.main.routes import main from minibase.main.routes import main
app.register_blueprint(users)
app.register_blueprint(posts)
app.register_blueprint(main) app.register_blueprint(main)
# Retunr The ccreated app # Retunr The ccreated app
return app return app

@ -1,5 +1,9 @@
import os import os
class themeMinibase():
image="rounded-circle account-img"
class Config: class Config:
# (FLASK) Sectret key wich will be used to secure some requests an connections # (FLASK) Sectret key wich will be used to secure some requests an connections
SECRET_KEY = os.environ.get('MINIBASE_SECRET_KEY') SECRET_KEY = os.environ.get('MINIBASE_SECRET_KEY')

@ -0,0 +1,16 @@
from flask import Blueprint, render_template
errors = Blueprint('errors', __name__)
@errors.app_errorhandler(404)
def error_404(error):
return render_template('errors/404.html'), 404
@errors.app_errorhandler(403)
def error_403(error):
return render_template('errors/403.html'), 403
@errors.app_errorhandler(500)
def error_500(error):
return render_template('errors/500.html'), 500

@ -4,6 +4,8 @@ from flask_login import current_user, login_required
from minibase import db from minibase import db
from minibase.models import Post from minibase.models import Post
from minibase.posts.forms import postForm from minibase.posts.forms import postForm
from minibase.config import themeMinibase
# Declaring a blueprint # Declaring a blueprint
posts = Blueprint('posts', __name__) posts = Blueprint('posts', __name__)
@ -23,7 +25,7 @@ def new_post():
@posts.route("/post/<int:post_id>") @posts.route("/post/<int:post_id>")
def post(post_id): def post(post_id):
post = Post.query.get_or_404(post_id) post = Post.query.get_or_404(post_id)
return render_template('post.html', title=post.title, post=post) return render_template('post.html', title=post.title, post=post, theme=themeMinibase)
@posts.route("/post/<int:post_id>/update", methods=['GET', 'POST']) @posts.route("/post/<int:post_id>/update", methods=['GET', 'POST'])
@login_required @login_required

Binary file not shown.

@ -1,80 +1,4 @@
body { .bg-kynsight {
background: #fafafa; background-color: #191717;
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;
}

@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<h1>Your dont have permission to do that (403)</h1>
<p>PLease Check your account and try again</p>
</div>
{% endblock content %}

@ -0,0 +1,9 @@
{% extends "layout.html" %}
{% block content %}
<div class="content-section">
<h1>Ooops :S Page Not Found (404)</h1>
<p>That page doens not exist. Please try another one</p>
</div>
{% endblock content %}

@ -0,0 +1,9 @@
{% extends "layout.html" %}
cat: templates/errors/500.html: No such file or directory
<div class="content-section">
<h1>Something Went Wrong (500)</h1>
<p>There Was an Error from our side please give us a moment and try again later</p>
</div>
{% endblock content %}

@ -2,9 +2,8 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css') }}">
{% if title %} {% if title %}
@ -14,62 +13,71 @@
{% endif %} {% endif %}
</head> </head>
<body> <body>
<header class="site-header"> <nav class="navbar navbar-expand-lg navbar-dark bg-kynsight">
<nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top"> <a class="navbar-brand" style="color: #f26628;" href="#">MiniBase</a>
<div class="container"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<a class="navbar-brand mr-4" href="{{ url_for('main.home') }}">MiniBase</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> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarToggle">
<div class="navbar-nav mr-auto"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<a class="nav-item nav-link" href="{{ url_for('main.home') }}">Home</a> <ul class="navbar-nav mr-auto">
<a class="nav-item nav-link" href="{{ url_for('main.about') }}">About</a> {% if current_user.is_authenticated %}
<li class="nav-item active"><a class="nav-link" href="#">Overwiev</a></li>
<li class="nav-item"><a class="nav-link" href="#">Projects</a></li>
<li class="nav-item"><a class="nav-link" href="#">Custommer</a></li>
<li class="nav-item"><a class="nav-link" href="#">Manufacturer</a></li>
<li class="nav-item"><a class="nav-link" href="#">Contacts</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Search By</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Project Name</a>
<a class="dropdown-item" href="#">Customer Name</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Project ID</a>
<a class="dropdown-item" href="#">Customer ID</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Cutsom Options</a>
</div> </div>
<!-- Navbar Right Side --> </li>
<div class="navbar-nav"> {% else %}
<li class="nav-item"><a class="nav-link disabled" href="#">Overwiev</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#">Projects</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#">Custommer</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#">Manufacturer</a></li>
<li class="nav-item"><a class="nav-link disabled" href="#">Contacts</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle disabled" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Search By</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#">Project Name</a>
<a class="dropdown-item" href="#">Customer Name</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Project ID</a>
<a class="dropdown-item" href="#">Customer ID</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#">Cutsom Options</a>
</div>
</li>
{% endif %}
</ul>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<a class="nav-item nav-link" href="{{ url_for('posts.new_post') }}">New Post</a> <li class="nav-item"><a class="nav-link disabled" href="#">Account</a></li>
<a class="nav-item nav-link" href="{{ url_for('users.logout') }}">Logout</a>
<a class="nav-item nav-link" href="{{ url_for('users.account') }}">Account</a>
{% else %} {% else %}
<a class="nav-item nav-link" href="{{ url_for('users.login') }}">Login</a> <li class="nav-item active"><a class="nav-link" href="#">Login<span class="sr-only">(current)</span></a></li>
<a class="nav-item nav-link" href="{{ url_for('users.register') }}">Register</a>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
</nav> </nav>
</header> <main>
<main role="main" class="container"> <h1>MiniBase</h1>
<div class="row"> {% if current_user.is_authenticated %}
<div class="col-md-8"> <h3>You are eutehnticated super !</h2>
{% with messages = get_flashed_messages(with_categories=true) %} {% else %}
{% if messages %} <h3>To see the infromations please login</h3>
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %} {% 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> </main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> <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://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
</body> </body>
</html> </html>

@ -2,7 +2,7 @@
{% block content %} {% block content %}
<article class="media content-section"> <article class="media content-section">
<img class="rounded-circle article-img" src="{{ url_for('static', filename='pics/' + post.author.image_file) }}"> <img class="{{ theme.image }} article-img" src="{{ url_for('static', filename='pics/' + post.author.image_file) }}">
<div class="media-body"> <div class="media-body">
<div class="article-metadata"> <div class="article-metadata">
<a class="mr-2" href="{{ url_for('users.user_posts', username=post.author.username) }}" >{{ post.author.username }}</a> <a class="mr-2" href="{{ url_for('users.user_posts', username=post.author.username) }}" >{{ post.author.username }}</a>

@ -1,5 +1,7 @@
from minibase import app from minibase import create_minibase
#Enable debug option even if run directly form Python3 #Enable debug option even if run directly form Python3
app=create_minibase()
if __name__ == '__main__': if __name__ == '__main__':
app.run(debug=True) app.run(debug=True)

Loading…
Cancel
Save