diff --git a/interface/__pycache__/dbCommands.cpython-311.pyc b/interface/__pycache__/dbCommands.cpython-311.pyc index f09d6a43..69137f3f 100644 Binary files a/interface/__pycache__/dbCommands.cpython-311.pyc and b/interface/__pycache__/dbCommands.cpython-311.pyc differ diff --git a/interface/__pycache__/dbProjects.cpython-311.pyc b/interface/__pycache__/dbProjects.cpython-311.pyc index daa2100d..96035c56 100644 Binary files a/interface/__pycache__/dbProjects.cpython-311.pyc and b/interface/__pycache__/dbProjects.cpython-311.pyc differ diff --git a/learningFlask/minibase/__init__.py b/learningFlask/minibase/__init__.py new file mode 100644 index 00000000..88cc2ce5 --- /dev/null +++ b/learningFlask/minibase/__init__.py @@ -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 diff --git a/learningFlask/minibase/__pycache__/__init__.cpython-311.pyc b/learningFlask/minibase/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 00000000..53b8fd67 Binary files /dev/null and b/learningFlask/minibase/__pycache__/__init__.cpython-311.pyc differ diff --git a/learningFlask/minibase/__pycache__/config.cpython-311.pyc b/learningFlask/minibase/__pycache__/config.cpython-311.pyc new file mode 100644 index 00000000..669959a2 Binary files /dev/null and b/learningFlask/minibase/__pycache__/config.cpython-311.pyc differ diff --git a/learningFlask/minibase/__pycache__/models.cpython-311.pyc b/learningFlask/minibase/__pycache__/models.cpython-311.pyc new file mode 100644 index 00000000..3117f34f Binary files /dev/null and b/learningFlask/minibase/__pycache__/models.cpython-311.pyc differ diff --git a/learningFlask/minibase/config.py b/learningFlask/minibase/config.py new file mode 100644 index 00000000..a17f72c4 --- /dev/null +++ b/learningFlask/minibase/config.py @@ -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 diff --git a/learningFlask/minibase/errors/__pycache__/handlers.cpython-311.pyc b/learningFlask/minibase/errors/__pycache__/handlers.cpython-311.pyc new file mode 100644 index 00000000..a18050a2 Binary files /dev/null and b/learningFlask/minibase/errors/__pycache__/handlers.cpython-311.pyc differ diff --git a/learningFlask/minibase/errors/handlers.py b/learningFlask/minibase/errors/handlers.py new file mode 100644 index 00000000..737cba33 --- /dev/null +++ b/learningFlask/minibase/errors/handlers.py @@ -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 + diff --git a/learningFlask/minibase/main/__init.py__ b/learningFlask/minibase/main/__init.py__ new file mode 100644 index 00000000..e69de29b diff --git a/learningFlask/minibase/main/__pycache__/routes.cpython-311.pyc b/learningFlask/minibase/main/__pycache__/routes.cpython-311.pyc new file mode 100644 index 00000000..04daa01f Binary files /dev/null and b/learningFlask/minibase/main/__pycache__/routes.cpython-311.pyc differ diff --git a/learningFlask/minibase/main/routes.py b/learningFlask/minibase/main/routes.py new file mode 100644 index 00000000..33ddd5f5 --- /dev/null +++ b/learningFlask/minibase/main/routes.py @@ -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') diff --git a/learningFlask/minibase/models.py b/learningFlask/minibase/models.py new file mode 100644 index 00000000..8fc03a9b --- /dev/null +++ b/learningFlask/minibase/models.py @@ -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}')" + diff --git a/learningFlask/minibase/posts/__init.py__ b/learningFlask/minibase/posts/__init.py__ new file mode 100644 index 00000000..e69de29b diff --git a/learningFlask/minibase/posts/__pycache__/forms.cpython-311.pyc b/learningFlask/minibase/posts/__pycache__/forms.cpython-311.pyc new file mode 100644 index 00000000..074296ce Binary files /dev/null and b/learningFlask/minibase/posts/__pycache__/forms.cpython-311.pyc differ diff --git a/learningFlask/minibase/posts/__pycache__/routes.cpython-311.pyc b/learningFlask/minibase/posts/__pycache__/routes.cpython-311.pyc new file mode 100644 index 00000000..a51a90d2 Binary files /dev/null and b/learningFlask/minibase/posts/__pycache__/routes.cpython-311.pyc differ diff --git a/learningFlask/minibase/posts/forms.py b/learningFlask/minibase/posts/forms.py new file mode 100644 index 00000000..e433ce9a --- /dev/null +++ b/learningFlask/minibase/posts/forms.py @@ -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') diff --git a/learningFlask/minibase/posts/routes.py b/learningFlask/minibase/posts/routes.py new file mode 100644 index 00000000..d5c145d9 --- /dev/null +++ b/learningFlask/minibase/posts/routes.py @@ -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/") +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//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//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')) diff --git a/learningFlask/minibase/site.db b/learningFlask/minibase/site.db new file mode 100644 index 00000000..3c4e3e00 Binary files /dev/null and b/learningFlask/minibase/site.db differ diff --git a/learningFlask/minibase/static/main.css b/learningFlask/minibase/static/main.css new file mode 100644 index 00000000..405c3451 --- /dev/null +++ b/learningFlask/minibase/static/main.css @@ -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; +} diff --git a/learningFlask/minibase/static/pics/328d4576bc7c982f.jpg b/learningFlask/minibase/static/pics/328d4576bc7c982f.jpg new file mode 100644 index 00000000..62f20a96 Binary files /dev/null and b/learningFlask/minibase/static/pics/328d4576bc7c982f.jpg differ diff --git a/learningFlask/minibase/static/pics/ca730803911d6ef1.jpg b/learningFlask/minibase/static/pics/ca730803911d6ef1.jpg new file mode 100644 index 00000000..e1436263 Binary files /dev/null and b/learningFlask/minibase/static/pics/ca730803911d6ef1.jpg differ diff --git a/learningFlask/minibase/static/pics/default.jpg b/learningFlask/minibase/static/pics/default.jpg new file mode 100644 index 00000000..38f286f6 Binary files /dev/null and b/learningFlask/minibase/static/pics/default.jpg differ diff --git a/learningFlask/minibase/templates/about.html b/learningFlask/minibase/templates/about.html new file mode 100644 index 00000000..2024e4de --- /dev/null +++ b/learningFlask/minibase/templates/about.html @@ -0,0 +1,5 @@ +{% extends "layout.html" %} + +{% block content %} +

About Page

+{% endblock content %} diff --git a/learningFlask/minibase/templates/account.html b/learningFlask/minibase/templates/account.html new file mode 100644 index 00000000..e803227b --- /dev/null +++ b/learningFlask/minibase/templates/account.html @@ -0,0 +1,60 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ +
+ +

{{ current_user.email }}

+
+
+
+ {{ form.hidden_tag() }} +
+ Account Info +
+ {{ form.username.label(class="form-control-label") }} + + {% if form.username.errors %} + {{ form.username(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.username.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.username(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.email.label(class="form-control-label") }} + {% if form.email.errors %} + {{ form.email(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.email.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.email(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.picture.label() }}
+ {{ form.picture(class="form-control-file") }}
+ {% if form.picture.errors %} + {% for error in form.picture.errors %} + {{ error }}
+ {% endfor %} + {% endif %} +
+
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+{% endblock content %} diff --git a/learningFlask/minibase/templates/create_post.html b/learningFlask/minibase/templates/create_post.html new file mode 100644 index 00000000..345234cd --- /dev/null +++ b/learningFlask/minibase/templates/create_post.html @@ -0,0 +1,40 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ {{ form.hidden_tag() }} +
+ {{ legend }} +
+ {{ form.title.label(class="form-control-label") }} + {% if form.title.errors %} + {{ form.title(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.title.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.title(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.content.label(class="form-control-label") }} + {% if form.content.errors %} + {{ form.content(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.content.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.content(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+{% endblock content %} diff --git a/learningFlask/minibase/templates/errors/403.html b/learningFlask/minibase/templates/errors/403.html new file mode 100644 index 00000000..fe39e932 --- /dev/null +++ b/learningFlask/minibase/templates/errors/403.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +{% block content %} +
+

Your dont have permission to do that (403)

+

PLease Check your account and try again

+
+ +{% endblock content %} diff --git a/learningFlask/minibase/templates/errors/404.html b/learningFlask/minibase/templates/errors/404.html new file mode 100644 index 00000000..f4156a41 --- /dev/null +++ b/learningFlask/minibase/templates/errors/404.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +{% block content %} +
+

Ooops :S Page Not Found (404)

+

That page doens not exist. Please try another one

+
+ +{% endblock content %} diff --git a/learningFlask/minibase/templates/errors/500.html b/learningFlask/minibase/templates/errors/500.html new file mode 100644 index 00000000..a6af401d --- /dev/null +++ b/learningFlask/minibase/templates/errors/500.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +cat: templates/errors/500.html: No such file or directory +
+

Something Went Wrong (500)

+

There Was an Error from our side please give us a moment and try again later

+
+ +{% endblock content %} diff --git a/learningFlask/minibase/templates/home.html b/learningFlask/minibase/templates/home.html new file mode 100644 index 00000000..fb7d0603 --- /dev/null +++ b/learningFlask/minibase/templates/home.html @@ -0,0 +1,28 @@ +{% extends "layout.html" %} + +{% block content %} + {% for post in posts.items %} + + {% 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 %} + {{ page_num }} + {% else %} + {{ page_num }} + {% endif %} + {% else %} + ... + {% endif %} + {% endfor %} +{% endblock content %} diff --git a/learningFlask/minibase/templates/layout.html b/learningFlask/minibase/templates/layout.html new file mode 100644 index 00000000..76489e75 --- /dev/null +++ b/learningFlask/minibase/templates/layout.html @@ -0,0 +1,75 @@ + + + + + + + + + + {% if title %} + MiniBase - {{ title }} + {% else %} + MiniBase + {% endif %} + + + +
+
+
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+
+
+

Our Sidebar

+

You can put any information here you'd like. +

    +
  • Latest Posts
  • +
  • Announcements
  • +
  • Calendars
  • +
  • etc
  • +
+

+
+
+
+
+ + + diff --git a/learningFlask/minibase/templates/login.html b/learningFlask/minibase/templates/login.html new file mode 100644 index 00000000..cce53f8a --- /dev/null +++ b/learningFlask/minibase/templates/login.html @@ -0,0 +1,52 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ {{ form.hidden_tag() }} +
+ Log In +
+ {{ form.email.label(class="form-control-label") }} + {% if form.email.errors %} + {{ form.email(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.email.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.email(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.password.label(class="form-control-label") }} + {% if form.password.errors %} + {{ form.password(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.password.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.password(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.remember(class="form-check-input") }} + {{ form.remember.label(class="form-check-label") }} +
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+ + Forgot Password? + +
+
+
+ + Need An Account? Sign Up Now + +
+{% endblock content %} diff --git a/learningFlask/minibase/templates/post.html b/learningFlask/minibase/templates/post.html new file mode 100644 index 00000000..b07d28e5 --- /dev/null +++ b/learningFlask/minibase/templates/post.html @@ -0,0 +1,40 @@ +{% extends "layout.html" %} + +{% block content %} +
+ +
+ +

{{ post.title }}

+

{{ post.content }}

+
+
+ + +{% endblock content %} diff --git a/learningFlask/minibase/templates/register.html b/learningFlask/minibase/templates/register.html new file mode 100644 index 00000000..3d2c8cef --- /dev/null +++ b/learningFlask/minibase/templates/register.html @@ -0,0 +1,72 @@ +{% extends "layout.html" %} +{% block content %} +
+ + {{ form.hidden_tag() }} +
+ Join Today +
+ {{ form.username.label(class="form-control-label") }} + + {% if form.username.errors %} + {{ form.username(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.username.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.username(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.email.label(class="form-control-label") }} + {% if form.email.errors %} + {{ form.email(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.email.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.email(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.password.label(class="form-control-label") }} + {% if form.password.errors %} + {{ form.password(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.password.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.password(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.password_confirm.label(class="form-control-label") }} + {% if form.password_confirm.errors %} + {{ form.password_confirm(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.password_confirm.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.password_confirm(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+ +
+
+ + Already Have An Account? Sign In + +
+{% endblock content %} diff --git a/learningFlask/minibase/templates/reset_request.html b/learningFlask/minibase/templates/reset_request.html new file mode 100644 index 00000000..bc3863c1 --- /dev/null +++ b/learningFlask/minibase/templates/reset_request.html @@ -0,0 +1,27 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ {{ form.hidden_tag() }} +
+ Reset Password +
+ {{ form.email.label(class="form-control-label") }} + {% if form.email.errors %} + {{ form.email(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.email.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.email(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+{% endblock content %} diff --git a/learningFlask/minibase/templates/reset_token.html b/learningFlask/minibase/templates/reset_token.html new file mode 100644 index 00000000..f0ed2b78 --- /dev/null +++ b/learningFlask/minibase/templates/reset_token.html @@ -0,0 +1,40 @@ +{% extends "layout.html" %} +{% block content %} +
+
+ {{ form.hidden_tag() }} +
+ Reset Password +
+ {{ form.password.label(class="form-control-label") }} + {% if form.password.errors %} + {{ form.password(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.password.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.password(class="form-control form-control-lg") }} + {% endif %} +
+
+ {{ form.password_confirm.label(class="form-control-label") }} + {% if form.password_confirm.errors %} + {{ form.password_confirm(class="form-control form-control-lg is-invalid") }} +
+ {% for error in form.password_confirm.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.password_confirm(class="form-control form-control-lg") }} + {% endif %} +
+
+
+ {{ form.submit(class="btn btn-outline-info") }} +
+
+
+{% endblock content %} diff --git a/learningFlask/minibase/templates/user_posts.html b/learningFlask/minibase/templates/user_posts.html new file mode 100644 index 00000000..b404428a --- /dev/null +++ b/learningFlask/minibase/templates/user_posts.html @@ -0,0 +1,29 @@ +{% extends "layout.html" %} + +{% block content %} +

Posts By {{ user.username }} ({{ posts.total }})

+ {% for post in posts.items %} + + {% 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 %} + {{ page_num }} + {% else %} + {{ page_num }} + {% endif %} + {% else %} + ... + {% endif %} + {% endfor %} +{% endblock content %} diff --git a/learningFlask/minibase/users/__init.py__ b/learningFlask/minibase/users/__init.py__ new file mode 100644 index 00000000..e69de29b diff --git a/learningFlask/minibase/users/__pycache__/forms.cpython-311.pyc b/learningFlask/minibase/users/__pycache__/forms.cpython-311.pyc new file mode 100644 index 00000000..6708f055 Binary files /dev/null and b/learningFlask/minibase/users/__pycache__/forms.cpython-311.pyc differ diff --git a/learningFlask/minibase/users/__pycache__/routes.cpython-311.pyc b/learningFlask/minibase/users/__pycache__/routes.cpython-311.pyc new file mode 100644 index 00000000..0e335446 Binary files /dev/null and b/learningFlask/minibase/users/__pycache__/routes.cpython-311.pyc differ diff --git a/learningFlask/minibase/users/__pycache__/utils.cpython-311.pyc b/learningFlask/minibase/users/__pycache__/utils.cpython-311.pyc new file mode 100644 index 00000000..03118550 Binary files /dev/null and b/learningFlask/minibase/users/__pycache__/utils.cpython-311.pyc differ diff --git a/learningFlask/minibase/users/forms.py b/learningFlask/minibase/users/forms.py new file mode 100644 index 00000000..50354a53 --- /dev/null +++ b/learningFlask/minibase/users/forms.py @@ -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') + diff --git a/learningFlask/minibase/users/routes.py b/learningFlask/minibase/users/routes.py new file mode 100644 index 00000000..1f09feca --- /dev/null +++ b/learningFlask/minibase/users/routes.py @@ -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/") +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/", 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) diff --git a/learningFlask/minibase/users/utils.py b/learningFlask/minibase/users/utils.py new file mode 100644 index 00000000..c30ff03d --- /dev/null +++ b/learningFlask/minibase/users/utils.py @@ -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) diff --git a/learningFlask/run.py b/learningFlask/run.py new file mode 100644 index 00000000..8ac58c70 --- /dev/null +++ b/learningFlask/run.py @@ -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) diff --git a/learningFlask/site.db b/learningFlask/site.db new file mode 100644 index 00000000..e48916f7 Binary files /dev/null and b/learningFlask/site.db differ diff --git a/webinterface/minibase/__init__.py b/webinterface/minibase/__init__.py index d02dfb5a..397034ef 100644 --- a/webinterface/minibase/__init__.py +++ b/webinterface/minibase/__init__.py @@ -13,7 +13,7 @@ bcrypt = Bcrypt() # (LOGIN) Login manage plugin configuration login_manager = LoginManager() -login_manager.login_view = 'login' +login_manager.login_view = 'users.login' login_manager.login_message_category = 'info' #Boostrap Info Message # (EMAIL AGENT) Definition @@ -32,11 +32,7 @@ def create_minibase(config_class=Config): # (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 - app.register_blueprint(users) - app.register_blueprint(posts) app.register_blueprint(main) # Retunr The ccreated app return app diff --git a/webinterface/minibase/__pycache__/__init__.cpython-311.pyc b/webinterface/minibase/__pycache__/__init__.cpython-311.pyc index 382b7812..f95f77ab 100644 Binary files a/webinterface/minibase/__pycache__/__init__.cpython-311.pyc and b/webinterface/minibase/__pycache__/__init__.cpython-311.pyc differ diff --git a/webinterface/minibase/__pycache__/config.cpython-311.pyc b/webinterface/minibase/__pycache__/config.cpython-311.pyc index 0b0980f9..f4994b32 100644 Binary files a/webinterface/minibase/__pycache__/config.cpython-311.pyc and b/webinterface/minibase/__pycache__/config.cpython-311.pyc differ diff --git a/webinterface/minibase/config.py b/webinterface/minibase/config.py index 1d06dc9f..a17f72c4 100644 --- a/webinterface/minibase/config.py +++ b/webinterface/minibase/config.py @@ -1,5 +1,9 @@ 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') diff --git a/webinterface/minibase/errors/__pycache__/handlers.cpython-311.pyc b/webinterface/minibase/errors/__pycache__/handlers.cpython-311.pyc new file mode 100644 index 00000000..63ec01f3 Binary files /dev/null and b/webinterface/minibase/errors/__pycache__/handlers.cpython-311.pyc differ diff --git a/webinterface/minibase/errors/handlers.py b/webinterface/minibase/errors/handlers.py new file mode 100644 index 00000000..737cba33 --- /dev/null +++ b/webinterface/minibase/errors/handlers.py @@ -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 + diff --git a/webinterface/minibase/posts/__pycache__/routes.cpython-311.pyc b/webinterface/minibase/posts/__pycache__/routes.cpython-311.pyc index b6ad163e..b2bce97a 100644 Binary files a/webinterface/minibase/posts/__pycache__/routes.cpython-311.pyc and b/webinterface/minibase/posts/__pycache__/routes.cpython-311.pyc differ diff --git a/webinterface/minibase/posts/routes.py b/webinterface/minibase/posts/routes.py index b3ed2eba..d5c145d9 100644 --- a/webinterface/minibase/posts/routes.py +++ b/webinterface/minibase/posts/routes.py @@ -4,6 +4,8 @@ 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__) @@ -23,7 +25,7 @@ def new_post(): @posts.route("/post/") def post(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//update", methods=['GET', 'POST']) @login_required diff --git a/webinterface/minibase/site.db b/webinterface/minibase/site.db index 026ba317..3c4e3e00 100644 Binary files a/webinterface/minibase/site.db and b/webinterface/minibase/site.db differ diff --git a/webinterface/minibase/static/main.css b/webinterface/minibase/static/main.css index c05529fe..4c8c437d 100644 --- a/webinterface/minibase/static/main.css +++ b/webinterface/minibase/static/main.css @@ -1,80 +1,4 @@ -body { - background: #fafafa; - color: #333333; - margin-top: 5rem; +.bg-kynsight { + background-color: #191717; } -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; -} diff --git a/webinterface/minibase/templates/errors/403.html b/webinterface/minibase/templates/errors/403.html new file mode 100644 index 00000000..fe39e932 --- /dev/null +++ b/webinterface/minibase/templates/errors/403.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +{% block content %} +
+

Your dont have permission to do that (403)

+

PLease Check your account and try again

+
+ +{% endblock content %} diff --git a/webinterface/minibase/templates/errors/404.html b/webinterface/minibase/templates/errors/404.html new file mode 100644 index 00000000..f4156a41 --- /dev/null +++ b/webinterface/minibase/templates/errors/404.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +{% block content %} +
+

Ooops :S Page Not Found (404)

+

That page doens not exist. Please try another one

+
+ +{% endblock content %} diff --git a/webinterface/minibase/templates/errors/500.html b/webinterface/minibase/templates/errors/500.html new file mode 100644 index 00000000..a6af401d --- /dev/null +++ b/webinterface/minibase/templates/errors/500.html @@ -0,0 +1,9 @@ +{% extends "layout.html" %} + +cat: templates/errors/500.html: No such file or directory +
+

Something Went Wrong (500)

+

There Was an Error from our side please give us a moment and try again later

+
+ +{% endblock content %} diff --git a/webinterface/minibase/templates/layout.html b/webinterface/minibase/templates/layout.html index 27afe0ec..07d201e8 100644 --- a/webinterface/minibase/templates/layout.html +++ b/webinterface/minibase/templates/layout.html @@ -2,9 +2,8 @@ - - - + + {% if title %} @@ -14,62 +13,71 @@ {% endif %} - -
-
-
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
- {{ message }} -
- {% endfor %} + + {% endif %} + + {% if current_user.is_authenticated %} + + {% else %} + {% endif %} - {% endwith %} - {% block content %}{% endblock %} -
-
-
-

Our Sidebar

-

You can put any information here you'd like. -

    -
  • Latest Posts
  • -
  • Announcements
  • -
  • Calendars
  • -
  • etc
  • -
-

-
-
+ +
+

MiniBase

+ {% if current_user.is_authenticated %} +

You are eutehnticated super !

+ {% else %} +

To see the infromations please login

+ {% endif %}
- + + + diff --git a/webinterface/minibase/templates/post.html b/webinterface/minibase/templates/post.html index 434797e7..b07d28e5 100644 --- a/webinterface/minibase/templates/post.html +++ b/webinterface/minibase/templates/post.html @@ -2,7 +2,7 @@ {% block content %}
- +