├── .dockerignore ├── .env ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE.md ├── README.md ├── apps ├── __init__.py ├── authentication │ ├── __init__.py │ ├── forms.py │ ├── models.py │ ├── routes.py │ └── util.py ├── config.py ├── home │ ├── __init__.py │ └── routes.py ├── static │ ├── assets │ │ ├── .gitkeep │ │ ├── css │ │ │ ├── material-kit.css │ │ │ ├── material-kit.css.map │ │ │ ├── material-kit.min.css │ │ │ ├── nucleo-icons.css │ │ │ └── nucleo-svg.css │ │ ├── fonts │ │ │ ├── nucleo-icons.eot │ │ │ ├── nucleo-icons.svg │ │ │ ├── nucleo-icons.ttf │ │ │ ├── nucleo-icons.woff │ │ │ ├── nucleo-icons.woff2 │ │ │ ├── nucleo.eot │ │ │ ├── nucleo.ttf │ │ │ ├── nucleo.woff │ │ │ └── nucleo.woff2 │ │ ├── gulpfile.js │ │ ├── img │ │ │ ├── apple-icon.png │ │ │ ├── bg.jpg │ │ │ ├── bg0.jpg │ │ │ ├── bg10.jpg │ │ │ ├── bg2.jpg │ │ │ ├── bg3.jpg │ │ │ ├── bg5.jpg │ │ │ ├── bg9.jpg │ │ │ ├── bruce-mars.jpg │ │ │ ├── city-profile.jpg │ │ │ ├── down-arrow-dark.svg │ │ │ ├── down-arrow-white.svg │ │ │ ├── down-arrow.svg │ │ │ ├── examples │ │ │ │ ├── blog-9-4.jpg │ │ │ │ ├── blog2.jpg │ │ │ │ ├── testimonial-6-2.jpg │ │ │ │ └── testimonial-6-3.jpg │ │ │ ├── favicon.png │ │ │ ├── illustrations │ │ │ │ └── illustration-signin.jpg │ │ │ ├── ivana-square.jpg │ │ │ ├── ivana-squares.jpg │ │ │ ├── logo-ct-dark.png │ │ │ ├── logos │ │ │ │ └── gray-logos │ │ │ │ │ ├── logo-apple.svg │ │ │ │ │ ├── logo-coinbase.svg │ │ │ │ │ ├── logo-digitalocean.svg │ │ │ │ │ ├── logo-facebook.svg │ │ │ │ │ ├── logo-nasa.svg │ │ │ │ │ ├── logo-netflix.svg │ │ │ │ │ ├── logo-pinterest.svg │ │ │ │ │ ├── logo-spotify.svg │ │ │ │ │ └── logo-vodafone.svg │ │ │ ├── macbook.png │ │ │ ├── shapes │ │ │ │ └── waves-white.svg │ │ │ ├── team-1.jpg │ │ │ ├── team-2.jpg │ │ │ ├── team-3.jpg │ │ │ ├── team-4.jpg │ │ │ └── team-5.jpg │ │ ├── js │ │ │ ├── core │ │ │ │ ├── bootstrap.bundle.min.js │ │ │ │ ├── bootstrap.min.js │ │ │ │ └── popper.min.js │ │ │ ├── material-kit.js │ │ │ ├── material-kit.js.map │ │ │ ├── material-kit.min.js │ │ │ └── plugins │ │ │ │ ├── choices.min.js │ │ │ │ ├── countup.min.js │ │ │ │ ├── flatpickr.min.js │ │ │ │ ├── highlight.min.js │ │ │ │ ├── moment.min.js │ │ │ │ ├── parallax.min.js │ │ │ │ ├── perfect-scrollbar.min.js │ │ │ │ ├── prism.min.js │ │ │ │ ├── rellax.min.js │ │ │ │ ├── tilt.min.js │ │ │ │ └── typedjs.js │ │ ├── package.json │ │ └── scss │ │ │ ├── material-kit.scss │ │ │ └── material-kit │ │ │ ├── _accordion.scss │ │ │ ├── _alert.scss │ │ │ ├── _avatars.scss │ │ │ ├── _backgrounds.scss │ │ │ ├── _badge.scss │ │ │ ├── _breadcrumbs.scss │ │ │ ├── _buttons.scss │ │ │ ├── _cards-extend.scss │ │ │ ├── _cards.scss │ │ │ ├── _components.scss │ │ │ ├── _dark-version.scss │ │ │ ├── _dropdown-extend.scss │ │ │ ├── _dropdown.scss │ │ │ ├── _dropup.scss │ │ │ ├── _fixed-plugin.scss │ │ │ ├── _floating-elements.scss │ │ │ ├── _footer.scss │ │ │ ├── _forms.scss │ │ │ ├── _gradients.scss │ │ │ ├── _header.scss │ │ │ ├── _icons.scss │ │ │ ├── _info-areas.scss │ │ │ ├── _list-check.scss │ │ │ ├── _misc-extend.scss │ │ │ ├── _misc.scss │ │ │ ├── _nav.scss │ │ │ ├── _navbar-vertical.scss │ │ │ ├── _navbar.scss │ │ │ ├── _pagination.scss │ │ │ ├── _popovers.scss │ │ │ ├── _progress.scss │ │ │ ├── _ripple.scss │ │ │ ├── _rtl-extend.scss │ │ │ ├── _rtl.scss │ │ │ ├── _social-buttons.scss │ │ │ ├── _tables.scss │ │ │ ├── _tilt.scss │ │ │ ├── _timeline.scss │ │ │ ├── _tooltips.scss │ │ │ ├── _typography.scss │ │ │ ├── _utilities-extend.scss │ │ │ ├── _utilities.scss │ │ │ ├── _variables.scss │ │ │ ├── badges │ │ │ ├── _badge-circle.scss │ │ │ ├── _badge-dot.scss │ │ │ ├── _badge-floating.scss │ │ │ └── _badge.scss │ │ │ ├── bootstrap │ │ │ ├── _accordion.scss │ │ │ ├── _alert.scss │ │ │ ├── _badge.scss │ │ │ ├── _breadcrumb.scss │ │ │ ├── _button-group.scss │ │ │ ├── _buttons.scss │ │ │ ├── _card.scss │ │ │ ├── _carousel.scss │ │ │ ├── _close.scss │ │ │ ├── _containers.scss │ │ │ ├── _dropdown.scss │ │ │ ├── _forms.scss │ │ │ ├── _functions.scss │ │ │ ├── _grid.scss │ │ │ ├── _helpers.scss │ │ │ ├── _images.scss │ │ │ ├── _list-group.scss │ │ │ ├── _mixins.scss │ │ │ ├── _modal.scss │ │ │ ├── _nav.scss │ │ │ ├── _navbar.scss │ │ │ ├── _offcanvas.scss │ │ │ ├── _pagination.scss │ │ │ ├── _placeholders.scss │ │ │ ├── _popover.scss │ │ │ ├── _progress.scss │ │ │ ├── _reboot.scss │ │ │ ├── _root.scss │ │ │ ├── _spinners.scss │ │ │ ├── _tables.scss │ │ │ ├── _toasts.scss │ │ │ ├── _tooltip.scss │ │ │ ├── _transitions.scss │ │ │ ├── _type.scss │ │ │ ├── _utilities.scss │ │ │ ├── _variables.scss │ │ │ ├── bootstrap-grid.scss │ │ │ ├── bootstrap-reboot.scss │ │ │ ├── bootstrap-utilities.scss │ │ │ ├── bootstrap.scss │ │ │ ├── forms │ │ │ │ ├── _floating-labels.scss │ │ │ │ ├── _form-check.scss │ │ │ │ ├── _form-control.scss │ │ │ │ ├── _form-range.scss │ │ │ │ ├── _form-select.scss │ │ │ │ ├── _form-text.scss │ │ │ │ ├── _input-group.scss │ │ │ │ ├── _labels.scss │ │ │ │ └── _validation.scss │ │ │ ├── helpers │ │ │ │ ├── _clearfix.scss │ │ │ │ ├── _colored-links.scss │ │ │ │ ├── _position.scss │ │ │ │ ├── _ratio.scss │ │ │ │ ├── _stacks.scss │ │ │ │ ├── _stretched-link.scss │ │ │ │ ├── _text-truncation.scss │ │ │ │ ├── _visually-hidden.scss │ │ │ │ └── _vr.scss │ │ │ ├── mixins │ │ │ │ ├── _alert.scss │ │ │ │ ├── _backdrop.scss │ │ │ │ ├── _border-radius.scss │ │ │ │ ├── _box-shadow.scss │ │ │ │ ├── _breakpoints.scss │ │ │ │ ├── _buttons.scss │ │ │ │ ├── _caret.scss │ │ │ │ ├── _clearfix.scss │ │ │ │ ├── _color-scheme.scss │ │ │ │ ├── _container.scss │ │ │ │ ├── _deprecate.scss │ │ │ │ ├── _forms.scss │ │ │ │ ├── _gradients.scss │ │ │ │ ├── _grid.scss │ │ │ │ ├── _image.scss │ │ │ │ ├── _list-group.scss │ │ │ │ ├── _lists.scss │ │ │ │ ├── _pagination.scss │ │ │ │ ├── _reset-text.scss │ │ │ │ ├── _resize.scss │ │ │ │ ├── _table-variants.scss │ │ │ │ ├── _text-truncate.scss │ │ │ │ ├── _transition.scss │ │ │ │ ├── _utilities.scss │ │ │ │ └── _visually-hidden.scss │ │ │ ├── utilities │ │ │ │ └── _api.scss │ │ │ └── vendor │ │ │ │ └── _rfs.scss │ │ │ ├── cards │ │ │ ├── card-background.scss │ │ │ ├── card-blog.scss │ │ │ ├── card-horizontal.scss │ │ │ ├── card-pricing.scss │ │ │ ├── card-profile.scss │ │ │ └── card-rotate.scss │ │ │ ├── custom │ │ │ ├── _styles.scss │ │ │ └── _variables.scss │ │ │ ├── forms │ │ │ ├── _form-check.scss │ │ │ ├── _form-select.scss │ │ │ ├── _form-switch.scss │ │ │ ├── _forms.scss │ │ │ ├── _input-group.scss │ │ │ ├── _inputs.scss │ │ │ └── _labels.scss │ │ │ ├── mixins │ │ │ ├── _badge.scss │ │ │ ├── _buttons.scss │ │ │ ├── _colored-shadows.scss │ │ │ ├── _hover.scss │ │ │ ├── _social-buttons.scss │ │ │ ├── _vendor.scss │ │ │ └── mixins.scss │ │ │ ├── plugins │ │ │ ├── free │ │ │ │ ├── _flatpickr.scss │ │ │ │ ├── _nouislider.scss │ │ │ │ ├── _perfect-scrollbar.scss │ │ │ │ ├── _prism.scss │ │ │ │ └── plugins.scss │ │ │ └── pro │ │ │ │ ├── _carousel-slick.scss │ │ │ │ ├── _choices.scss │ │ │ │ ├── _datatable-extend.scss │ │ │ │ ├── _datatable.scss │ │ │ │ ├── _dragula.scss │ │ │ │ ├── _dropzone.scss │ │ │ │ ├── _fullcalendar-extend.scss │ │ │ │ ├── _fullcalendar.scss │ │ │ │ ├── _glidejs.scss │ │ │ │ ├── _highlight.scss │ │ │ │ ├── _kanban.scss │ │ │ │ ├── _leaflet.scss │ │ │ │ ├── _list-check.scss │ │ │ │ ├── _photoswipe.scss │ │ │ │ ├── _quill.scss │ │ │ │ ├── _rating-widget.scss │ │ │ │ ├── _sweetalert2-extend.scss │ │ │ │ ├── _sweetalert2.scss │ │ │ │ ├── _vector-map.scss │ │ │ │ ├── multi-step.scss │ │ │ │ └── plugins-extend.scss │ │ │ ├── theme-pro.scss │ │ │ ├── theme.scss │ │ │ └── variables │ │ │ ├── _animations.scss │ │ │ ├── _avatars.scss │ │ │ ├── _badge.scss │ │ │ ├── _breadcrumb.scss │ │ │ ├── _cards-extend.scss │ │ │ ├── _cards.scss │ │ │ ├── _choices.scss │ │ │ ├── _dark-version.scss │ │ │ ├── _dropdowns.scss │ │ │ ├── _fixed-plugin.scss │ │ │ ├── _form-switch.scss │ │ │ ├── _full-calendar.scss │ │ │ ├── _header.scss │ │ │ ├── _info-areas.scss │ │ │ ├── _misc-extend.scss │ │ │ ├── _misc.scss │ │ │ ├── _navbar-vertical.scss │ │ │ ├── _navbar.scss │ │ │ ├── _pagination.scss │ │ │ ├── _ripple.scss │ │ │ ├── _rtl.scss │ │ │ ├── _social-buttons.scss │ │ │ ├── _table.scss │ │ │ ├── _timeline.scss │ │ │ ├── _utilities-extend.scss │ │ │ ├── _utilities.scss │ │ │ └── _virtual-reality.scss │ ├── favicon.ico │ └── sitemap.xml └── templates │ ├── .gitkeep │ ├── accounts │ ├── login.html │ └── register.html │ ├── home │ ├── about-us.html │ ├── author.html │ ├── contact-us.html │ ├── index.html │ ├── page-403.html │ ├── page-404.html │ ├── page-500.html │ ├── sign-in.html │ ├── sign-up.html │ ├── template.html │ ├── ui-catchers-alerts.html │ ├── ui-catchers-modals.html │ ├── ui-catchers-tooltips-popovers.html │ ├── ui-elements-avatars.html │ ├── ui-elements-badges.html │ ├── ui-elements-breadcrumbs.html │ ├── ui-elements-buttons.html │ ├── ui-elements-dropdowns.html │ ├── ui-elements-progress-bars.html │ ├── ui-elements-toggles.html │ ├── ui-elements-typography.html │ ├── ui-input-forms.html │ ├── ui-input-inputs.html │ ├── ui-navigation-nav-tabs.html │ ├── ui-navigation-navbars.html │ ├── ui-navigation-pagination.html │ ├── ui-sections-features.html │ └── ui-sections-hero-sections.html │ ├── includes │ ├── footer-auth.html │ ├── footer-components.html │ ├── footer.html │ ├── navigation-light.html │ ├── navigation-transparent.html │ ├── navigation.html │ └── scripts.html │ └── layouts │ ├── base-fullscreen.html │ └── base.html ├── build.sh ├── docker-compose.yml ├── env.sample ├── gunicorn-cfg.py ├── nginx └── appseed-app.conf ├── render.yaml ├── requirements.txt └── run.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | __pycache__ 3 | *.pyc 4 | *.pyo 5 | *.pyd -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # True for development, False for production 2 | DEBUG=False 3 | 4 | # Flask ENV 5 | FLASK_APP=run.py 6 | FLASK_DEBUG=True 7 | 8 | # If not provided, a random one is generated 9 | # SECRET_KEY= 10 | 11 | # Used for CDN (in production) 12 | # No Slash at the end 13 | ASSETS_ROOT=/static/assets 14 | 15 | # If DB credentials (if NOT provided, or wrong values SQLite is used) 16 | # DB_ENGINE=mysql 17 | # DB_HOST=localhost 18 | # DB_NAME=appseed_db 19 | # DB_USERNAME=appseed_db_usr 20 | # DB_PASS=pass 21 | # DB_PORT=3306 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # tests and coverage 6 | *.pytest_cache 7 | .coverage 8 | 9 | # database & logs 10 | *.db 11 | *.sqlite3 12 | *.log 13 | 14 | # venv 15 | env 16 | venv 17 | 18 | # other 19 | .DS_Store 20 | 21 | # sphinx docs 22 | _build 23 | _static 24 | _templates 25 | 26 | # javascript 27 | package-lock.json 28 | .vscode/symbols.json 29 | 30 | apps/static/assets/node_modules 31 | apps/static/assets/yarn.lock 32 | apps/static/assets/.temp 33 | 34 | README_bk.md 35 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9 2 | 3 | # set environment variables 4 | ENV PYTHONDONTWRITEBYTECODE 1 5 | ENV PYTHONUNBUFFERED 1 6 | 7 | COPY requirements.txt . 8 | 9 | # install python dependencies 10 | RUN pip install --upgrade pip 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | COPY . . 14 | 15 | # gunicorn 16 | CMD ["gunicorn", "--config", "gunicorn-cfg.py", "run:app"] 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 - present [AppSeed](http://appseed.us/) 4 | 5 |
6 | 7 | ## Licensing Information 8 | 9 |
10 | 11 | | Item | - | 12 | | ---------------------------------- | --- | 13 | | License Type | MIT | 14 | | Use for print | **YES** | 15 | | Create single personal website/app | **YES** | 16 | | Create single website/app for client | **YES** | 17 | | Create multiple website/apps for clients | **YES** | 18 | | Create multiple SaaS applications | **YES** | 19 | | End-product paying users | **YES** | 20 | | Product sale | **YES** | 21 | | Remove footer credits | **YES** | 22 | | --- | --- | 23 | | Remove copyright mentions from source code | NO | 24 | | Production deployment assistance | NO | 25 | | Create HTML/CSS template for sale | NO | 26 | | Create Theme/Template for CMS for sale | NO | 27 | | Separate sale of our UI Elements | NO | 28 | 29 |
30 | 31 | --- 32 | For more information regarding licensing, please contact the AppSeed Service < *support@appseed.us* > 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Flask Material Kit](https://app-generator.dev/product/material-kit/flask/) 2 | 3 | Open-source **Flask Starter with Material Kit Design**, an open-source iconic `Bootstrap` design. 4 | The product is designed to deliver the best possible user experience with highly customizable feature-rich pages. 5 | 6 | - 👉 [Flask Material Kit](https://app-generator.dev/product/material-kit/flask/) - `Product Page` 7 | - 👉 [Flask Material Kit](https://flask-material-kit.appseed-srv1.com/) - `LIVE Demo` 8 | - 👉 [Flask Material Kit](https://app-generator.dev/docs/products/flask/material-kit/index.html) - `Complete Information` and Support Links 9 | - [Getting Started with Flask](https://app-generator.dev/docs/technologies/flask/index.html) - a `comprehensive tutorial` 10 | - `Configuration`: Install Tailwind/Flowbite, Prepare Environment, Setting up the Database 11 | - `Start with Docker` 12 | - `Manual Build` 13 | - `Start the project` 14 | - `Deploy on Render` 15 | 16 |
17 | 18 | ### Customize with [Flask App Generator](https://app-generator.dev/tools/flask-generator/) 19 | 20 | - Access the [App Generator](https://app-generator.dev/tools/flask-generator/) page 21 | - Select the preferred design 22 | - (Optional) Design Database: edit models and fields 23 | - (Optional) Edit the fields for the extended user model 24 | - (Optional) Enable OAuth for GitHub 25 | - (Optional) Add Celery (async tasks) 26 | - (Optional) Enable Dynamic Tables Module 27 | - Docker Scripts 28 | - Render CI/Cd Scripts 29 | 30 | **The generated Flask project is available as a ZIP Archive and also uploaded to GitHub.** 31 | 32 |
33 | 34 | ## Deploy on `Render` (free plan) 35 | 36 | [![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy) 37 | 38 |
39 | 40 | ## Features 41 | 42 | - Simple, Easy-to-Extend codebase, [Blueprint Pattern](https://app-generator.dev/blog/flask-blueprints-a-developers-guide/) 43 | - Up-to-date Dependencies 44 | - [Material Kit](https://app-generator.dev/docs/templates/bootstrap/material-kit.html) Full Integration 45 | - [Bootstrap](https://app-generator.dev/docs/templates/bootstrap/index.html) 5 Styling 46 | - Auth: Session Based, GitHub, Google 47 | - Celery Beat 48 | - DB Persistence: SQLite (default), 49 | - Easy switch to MySql/MariaDB, PgSql 50 | - Dynamic DataTables - manage data without coding 51 | - CI/CD integration for [Render](https://app-generator.dev/docs/deployment/render/index.html) 52 | - Deployment: Docker, Flask-Minify 53 | 54 | ![Material Kit - Starter generated by AppSeed.](https://user-images.githubusercontent.com/51070104/167396765-c88b7a95-155f-4236-8691-7b80fa2d9cd9.png) 55 | 56 |
57 | 58 | --- 59 | [Flask Material Kit](https://app-generator.dev/product/material-kit/flask/) - Open-Source **Flask** Starter provided by [App Generator](https://app-generator.dev) 60 | -------------------------------------------------------------------------------- /apps/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import os 7 | 8 | from flask import Flask 9 | from flask_login import LoginManager 10 | from flask_sqlalchemy import SQLAlchemy 11 | from importlib import import_module 12 | 13 | 14 | db = SQLAlchemy() 15 | login_manager = LoginManager() 16 | 17 | 18 | def register_extensions(app): 19 | db.init_app(app) 20 | login_manager.init_app(app) 21 | 22 | 23 | def register_blueprints(app): 24 | for module_name in ('authentication', 'home'): 25 | module = import_module('apps.{}.routes'.format(module_name)) 26 | app.register_blueprint(module.blueprint) 27 | 28 | 29 | def configure_database(app): 30 | 31 | @app.before_first_request 32 | def initialize_database(): 33 | try: 34 | db.create_all() 35 | except Exception as e: 36 | 37 | print('> Error: DBMS Exception: ' + str(e) ) 38 | 39 | # fallback to SQLite 40 | basedir = os.path.abspath(os.path.dirname(__file__)) 41 | app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'db.sqlite3') 42 | 43 | print('> Fallback to SQLite ') 44 | db.create_all() 45 | 46 | @app.teardown_request 47 | def shutdown_session(exception=None): 48 | db.session.remove() 49 | 50 | 51 | def create_app(config): 52 | app = Flask(__name__) 53 | app.config.from_object(config) 54 | register_extensions(app) 55 | register_blueprints(app) 56 | configure_database(app) 57 | return app 58 | -------------------------------------------------------------------------------- /apps/authentication/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask import Blueprint 7 | 8 | blueprint = Blueprint( 9 | 'authentication_blueprint', 10 | __name__, 11 | url_prefix='' 12 | ) 13 | -------------------------------------------------------------------------------- /apps/authentication/forms.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask_wtf import FlaskForm 7 | from wtforms import StringField, PasswordField 8 | from wtforms.validators import Email, DataRequired 9 | 10 | # login and registration 11 | 12 | 13 | class LoginForm(FlaskForm): 14 | username = StringField('Username', 15 | id='username_login', 16 | validators=[DataRequired()]) 17 | password = PasswordField('Password', 18 | id='pwd_login', 19 | validators=[DataRequired()]) 20 | 21 | 22 | class CreateAccountForm(FlaskForm): 23 | username = StringField('Username', 24 | id='username_create', 25 | validators=[DataRequired()]) 26 | email = StringField('Email', 27 | id='email_create', 28 | validators=[DataRequired(), Email()]) 29 | password = PasswordField('Password', 30 | id='pwd_create', 31 | validators=[DataRequired()]) 32 | -------------------------------------------------------------------------------- /apps/authentication/models.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask_login import UserMixin 7 | 8 | from apps import db, login_manager 9 | 10 | from apps.authentication.util import hash_pass 11 | 12 | class Users(db.Model, UserMixin): 13 | 14 | __tablename__ = 'Users' 15 | 16 | id = db.Column(db.Integer, primary_key=True) 17 | username = db.Column(db.String(64), unique=True) 18 | email = db.Column(db.String(64), unique=True) 19 | password = db.Column(db.LargeBinary) 20 | 21 | def __init__(self, **kwargs): 22 | for property, value in kwargs.items(): 23 | # depending on whether value is an iterable or not, we must 24 | # unpack it's value (when **kwargs is request.form, some values 25 | # will be a 1-element list) 26 | if hasattr(value, '__iter__') and not isinstance(value, str): 27 | # the ,= unpack of a singleton fails PEP8 (travis flake8 test) 28 | value = value[0] 29 | 30 | if property == 'password': 31 | value = hash_pass(value) # we need bytes here (not plain str) 32 | 33 | setattr(self, property, value) 34 | 35 | def __repr__(self): 36 | return str(self.username) 37 | 38 | 39 | @login_manager.user_loader 40 | def user_loader(id): 41 | return Users.query.filter_by(id=id).first() 42 | 43 | 44 | @login_manager.request_loader 45 | def request_loader(request): 46 | username = request.form.get('username') 47 | user = Users.query.filter_by(username=username).first() 48 | return user if user else None 49 | -------------------------------------------------------------------------------- /apps/authentication/util.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import os 7 | import hashlib 8 | import binascii 9 | 10 | # Inspiration -> https://www.vitoshacademy.com/hashing-passwords-in-python/ 11 | 12 | 13 | def hash_pass(password): 14 | """Hash a password for storing.""" 15 | 16 | salt = hashlib.sha256(os.urandom(60)).hexdigest().encode('ascii') 17 | pwdhash = hashlib.pbkdf2_hmac('sha512', password.encode('utf-8'), 18 | salt, 100000) 19 | pwdhash = binascii.hexlify(pwdhash) 20 | return (salt + pwdhash) # return bytes 21 | 22 | 23 | def verify_pass(provided_password, stored_password): 24 | """Verify a stored password against one provided by user""" 25 | 26 | stored_password = stored_password.decode('ascii') 27 | salt = stored_password[:64] 28 | stored_password = stored_password[64:] 29 | pwdhash = hashlib.pbkdf2_hmac('sha512', 30 | provided_password.encode('utf-8'), 31 | salt.encode('ascii'), 32 | 100000) 33 | pwdhash = binascii.hexlify(pwdhash).decode('ascii') 34 | return pwdhash == stored_password 35 | -------------------------------------------------------------------------------- /apps/config.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | import os, random, string 7 | 8 | class Config(object): 9 | 10 | basedir = os.path.abspath(os.path.dirname(__file__)) 11 | 12 | # Assets Management 13 | ASSETS_ROOT = os.getenv('ASSETS_ROOT', '/static/assets') 14 | 15 | # Set up the App SECRET_KEY 16 | SECRET_KEY = os.getenv('SECRET_KEY', 'Secr3t_55xA') 17 | 18 | SQLALCHEMY_TRACK_MODIFICATIONS = False 19 | 20 | DB_ENGINE = os.getenv('DB_ENGINE' , None) 21 | DB_USERNAME = os.getenv('DB_USERNAME' , None) 22 | DB_PASS = os.getenv('DB_PASS' , None) 23 | DB_HOST = os.getenv('DB_HOST' , None) 24 | DB_PORT = os.getenv('DB_PORT' , None) 25 | DB_NAME = os.getenv('DB_NAME' , None) 26 | 27 | USE_SQLITE = True 28 | 29 | # try to set up a Relational DBMS 30 | if DB_ENGINE and DB_NAME and DB_USERNAME: 31 | 32 | try: 33 | 34 | # Relational DBMS: PSQL, MySql 35 | SQLALCHEMY_DATABASE_URI = '{}://{}:{}@{}:{}/{}'.format( 36 | DB_ENGINE, 37 | DB_USERNAME, 38 | DB_PASS, 39 | DB_HOST, 40 | DB_PORT, 41 | DB_NAME 42 | ) 43 | 44 | USE_SQLITE = False 45 | 46 | except Exception as e: 47 | 48 | print('> Error: DBMS Exception: ' + str(e) ) 49 | print('> Fallback to SQLite ') 50 | 51 | if USE_SQLITE: 52 | 53 | # This will create a file in FOLDER 54 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'db.sqlite3') 55 | 56 | class ProductionConfig(Config): 57 | DEBUG = False 58 | 59 | # Security 60 | SESSION_COOKIE_HTTPONLY = True 61 | REMEMBER_COOKIE_HTTPONLY = True 62 | REMEMBER_COOKIE_DURATION = 3600 63 | 64 | class DebugConfig(Config): 65 | DEBUG = True 66 | 67 | 68 | # Load all possible configurations 69 | config_dict = { 70 | 'Production': ProductionConfig, 71 | 'Debug' : DebugConfig 72 | } 73 | -------------------------------------------------------------------------------- /apps/home/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from flask import Blueprint 7 | 8 | blueprint = Blueprint( 9 | 'home_blueprint', 10 | __name__, 11 | url_prefix='' 12 | ) 13 | -------------------------------------------------------------------------------- /apps/home/routes.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Copyright (c) 2019 - present AppSeed.us 4 | """ 5 | 6 | from apps.home import blueprint 7 | from flask import render_template, request 8 | from flask_login import login_required 9 | from jinja2 import TemplateNotFound 10 | 11 | 12 | @blueprint.route('/index') 13 | @login_required 14 | def index(): 15 | 16 | return render_template('home/index.html', segment='index') 17 | 18 | 19 | @blueprint.route('/