├── .env
├── .github
├── python-package.yml
└── workflows
│ └── publish-to-pypi.yml
├── .gitignore
├── EBGaramond-Medium.woff
├── EBGaramond-Medium.woff2
├── LICENSE.txt
├── MANIFEST.in
├── README.md
├── README_.md
├── documentation
├── 404.png
├── 4042.png
├── Screenshot (73).png
├── eight.png
├── eleven.png
├── filetool.png
├── five.png
├── four.png
├── fourteen.png
├── large.png
├── many.png
├── nine.png
├── one.png
├── server.png
├── serverr.png
├── seven.png
├── six.png
├── ten.png
├── thirteen.png
├── three.png
├── tomany.png
├── twelve.png
└── two.png
├── flask_quickstart_generator
├── __init__.py
├── cli.py
├── cmd_handler.py
├── css.py
├── env.py
├── forms.py
├── html.py
├── js.py
├── main.py
├── model.py
├── routes_.py
└── settings.py
├── requirements.txt
└── setup.py
/.env:
--------------------------------------------------------------------------------
1 | # twine upload dist/* -u __token__ -p => pypi token_id
--------------------------------------------------------------------------------
/.github/python-package.yml:
--------------------------------------------------------------------------------
1 | name: Publish Python distributions to PyPI and TestPyPI
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | python-build-n-publish:
8 | name: Build and publish Python distribution
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@main
12 |
13 | - name: Initialize Python 3.11.6
14 | uses: actions/setup-python@v1
15 | with:
16 | python-version: 3.11.6
17 |
18 | - name: Install dependencies
19 | run: |
20 | python -m pip install --upgrade pip
21 | pip install flake8
22 |
23 | - name: Lint with flake8
24 | run: |
25 | # stop the build if there are Python syntax errors or undefined names
26 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
27 | # exit-zero treats all errors as warnings.
28 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
29 |
30 | - name: Build binary wheel and a source tarball
31 | run: python setup.py sdist
32 |
33 | - name: Publish distribution to PyPI
34 | uses: pypa/gh-action-pypi-publish@master
35 | with:
36 | password: ${{ secrets.test_pypi_password }}
37 | repository_url: https://upload.pypi.org/legacy/
--------------------------------------------------------------------------------
/.github/workflows/publish-to-pypi.yml:
--------------------------------------------------------------------------------
1 | name: Publish Python package to PyPI
2 |
3 | on:
4 | push:
5 | tags:
6 | - "v*"
7 |
8 | jobs:
9 | build-and-publish:
10 | name: Build and publish package
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - uses: actions/checkout@v3
15 |
16 | - name: Set up Python
17 | uses: actions/setup-python@v4
18 | with:
19 | python-version: "3.11"
20 |
21 | - name: Install build tool
22 | run: pip install build
23 |
24 | - name: Build package
25 | run: python -m build
26 |
27 | - name: Publish to PyPI
28 | uses: pypa/gh-action-pypi-publish@release/v1
29 | with:
30 | user: __token__
31 | password: ${{ secrets.PYPI_API_TOKEN }}
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | *.db
3 | venv/
4 | config/.venv
5 | __pycache__
6 | /__pycache__
7 | **/*/__pycache__
8 |
--------------------------------------------------------------------------------
/EBGaramond-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/EBGaramond-Medium.woff
--------------------------------------------------------------------------------
/EBGaramond-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/EBGaramond-Medium.woff2
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright © 2024 Kennartech, https://ktecht.pythonanywhere.com/
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files Flask Boilerplate Generator, to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include requirements.txt
2 | include LICENSE.txt
3 | recursive-include documentation *
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introducing Flask Quickstart Generator.
2 |
3 |
4 | [](https://pepy.tech/project/flask-quickstart-generator) [](https://pepy.tech/project/flask-quickstart-generator) [](https://pepy.tech/project/flask-quickstart-generator)
5 |
6 |
7 | Are you tired of spending valuable time setting up the same boilerplate code every time you start a new Flask project? Say hello to Flask Quickstart Generator - your ultimate time-saving tool for Flask development!
8 |
9 | Flask Quickstart Generator streamlines the development process by automating the setup of a Flask project with just a few simple commands. Whether you're a seasoned Flask developer or just starting out, this tool will help you get your project up and running in no time.
10 |
11 |
12 | ## Install and update the package
13 | | Install and update |
14 | | ------------- |
15 | |pip install -U flask-quickstart-generator
16 |
17 |
18 | ## Version 1.1.3 – Features (April 6, 2025)
19 | | Features | Status | Details
20 | | ------------- | ------------- | -------- |
21 | |**Built-in Admin Dashboard:** |✅ | Comes with a fully integrated admin panel for managing key parts of the application.
22 | |**User Authentication System:**|✅ | Users must log in before accessing the dashboard, with role-based access control.
23 | |**Profile & Account Management:**|✅ | Authenticated users can view and update their profile details, including account settings.
24 | |**User Registration with Role Assignment:**|✅ | Supports user sign-up and assigning specific roles (e.g., admin, user).
25 | |**Comprehensive Error Handling Pages:**|✅ | Custom templates for HTTP errors like 400, 401, 403, 404, 413, 429, 500, and 503. Includes handling for exceptions like ValueError.
26 | |**Maintenance Mode (503):**|✅ | Shows a maintenance page when the app is under scheduled updates.
27 | |**Flash Messaging for Rate Limits:**|✅ | Uses Flask’s flash() to alert users (e.g., for 429 errors: too many requests).
28 | |**Session Timeout Auto Logout:**|✅ | Logs users out automatically after inactivity for better security.
29 | |**Responsive Design:**|✅ | Fully responsive layout that works across mobile, tablet, and desktop.
30 | |**Theme Customization:**|✅ | Offers pre-built themes or theme switching for a modern UI.
31 | |**Automatic DB Migration Init:**|✅ | Automatically checks for and initializes the Flask-Migrate folder via flask db init, saving setup time.
32 | |**Debug Logging Setup:**|✅ | Uses logging.basicConfig(level=logging.DEBUG) to provide detailed logs for pinpointing errors and tracking behavior.
33 | |**Color-Coded Terminal Logs:**|✅ | Adds colorful terminal outputs in middleware and response hooks to make request flow and security header injection easier to track.
34 |
35 |
36 | ## Features
37 | | Features | Status | Details
38 | | ------------- | ------------- | -------- |
39 | |**Effortless Setup:** |✅ | Simply install the package from PyPI OR github and use the command-line interface to create a new Flask project with a predefined folder structure and blueprint setup.
40 | |**Virtual Environment Management:**|✅ | Automatically sets up a virtual environment for your project, ensuring clean and isolated dependencies.
41 | |**Blueprint Configuration:**|✅ | Quickly scaffold your project with pre-configured blueprints for common application `components` like `authentication`, `administration`, `error handling`, `search`, `caching`, `db migrations`, `uploads`, `account settings`, `views`, `database`(model) and more.
42 | |**Security Enhancements:**|✅ | Includes built-in security measures such as CSRF protection, HTTP headers middleware, and secure session management to help keep your application safe from common vulnerabilities.
43 | |**logging:**|✅ | Easier Debugging Debug messages can include detailed information about variables, function calls, and the state of your application. This makes it easier to pinpoint where things might be going wrong.
44 | |**Flask-Compress:**|✅ | Flask-Compress is an extension for Flask that compresses the response data sent from your Flask application to the client. By compressing the data, it helps reduce the size of the response, leading to faster data transfer and reduced bandwidth usage
45 | |**login_manager, LoginManager:**|✅ | It handles user session management. This includes logging users in and out, managing session tokens, and providing user information to views.
46 |
47 |
48 | ## Robust Request Handling and Enhanced Security Measures
49 | | Features | Status | Details
50 | | ------------- | ------------- | -------- |
51 | |**URL Validation:**|✅ | Ensures valid request path format by rejecting paths without a leading slash or containing non-alphanumeric characters (excluding root URL).This helps prevent potential security issues and improves routing clarity.
52 | |**URL Canonicalization:**|✅ | Redirects URLs with uppercase letters to their lowercase equivalents. This promotes consistency and prevents duplicate content issues.
53 | |**Trailing Slash Removal:**|✅ | Removes trailing slashes from URLs (except for the root URL) to prevent duplicate content issues, search engine optimization (SEO) problems and duplicate content.
54 | |**Enhanced Security Measures:**|✅
55 |
56 | **In addition to features mentioned previously**
57 |
58 | | Features | Status | Details
59 | | ------------- | ------------- | -------- |
60 | |**Flask Minify:**|✅ | Flask Quickstart Generator integrates with Flask-Minify to automatically minify HTML, JavaScript and CSS files, improving page load times and performance.
61 | |**Rate Limiting**|✅ |Flask-Limiter, a powerful tool for implementing rate limiting in your application. This helps protect your server from malicious attacks by limiting the number of requests a client can send within a specific timeframe.
62 | |The default configuration in `__init__.py` sets a limit of 3000 requests per hour. You can easily customize this limit to fit your specific needs.
63 |
64 |
65 | # Benefits of Rate Limiting:
66 | | Benefits | Status | Details
67 | | ------------- | ------------- | -------- |
68 | |1. Mitigates denial-of-service (DoS) attacks by preventing a single user or automated script from overwhelming your server with requests.|✅ |✅
69 | |2. Improves application performance by ensuring resources are available to legitimate users.|✅ |✅
70 | |3. Enhances security by making it more difficult for attackers to exploit vulnerabilities.|✅ |✅
71 |
72 | ## Comprehensive Security Headers
73 | | Security Headers | Status | Details
74 | | ------------- | ------ | -------- |
75 | |**Clickjacking Protection:** |✅ |Sets the `X-Frame-Options` header to `DENY`, effectively blocking clickjacking attacks that attempt to trick users into clicking on malicious content within your application's frame.
76 | |**Referrer-Policy Control:** |✅ |Limits referrer information exposure using the `Referrer-Policy` header. This can be set to `strict-origin-when-cross-origin` to restrict referrer leaks to the same origin (your domain) when navigating away from your site in a cross-origin context. Alternatively, for stricter control, you can use `no-referrer` to prevent any referrer information from being sent.
77 | |**MIME-Sniffing Prevention:** |✅ |Configures `X-Content-Type-Options` to `nosniff`. This mitigates MIME-sniffing vulnerabilities where the browser attempts to guess the content type of a resource based on its initial bytes, potentially allowing attackers to inject malicious content.
78 | |**Content Security Policy (CSP):** |✅ |Implements a foundational CSP to restrict resource loading and enhance protection against XSS and data injection attacks. You can further customize this policy to specify allowed origins for scripts, stylesheets, fonts, images, and other resources. The provided example serves as a starting point:
79 | |**Strict-Transport-Security (STS):** |✅ |The Strict-Transport-Security header is used to enforce secure connections between the browser and the server by instructing the browser to only communicate with the server over HTTPS. Here's what each part of the header does:
80 | Purpose:
81 | Prevent downgrade attacks: It prevents attackers from tricking users into using HTTP instead of HTTPS.
82 | Protect against cookie hijacking: Cookies sent over HTTPS are encrypted, making it harder for attackers to steal session cookies.
83 |
84 |
85 | **Increased Developer Efficiency:**
86 |
87 | With Flask Quickstart Generator, you can focus on building the core functionalities of your application instead of wasting time on repetitive setup tasks. This translates to faster development cycles, improved productivity, and a more enjoyable development experience.
88 |
89 | Flask Quickstart Generator empowers you with a comprehensive set of features to streamline request processing and safeguard your application against common web vulnerabilities.
90 |
91 |
92 | ## User Commands: To Get Started Quickly
93 |
94 | Flask Quickstart Generator provides convenient commands to streamline your project setup:
95 |
96 | **Create Project Folder:**
97 |
98 | `Bash or Terminal`
99 |
100 | **flask-manage create-app my_demo_app**
101 |
102 | (This command creates Flask project with a predefined folder structure and blueprint setup)
103 |
104 | **===Create Virtual Environment:===**
105 |
106 | `Bash or Terminal`
107 |
108 | **flask-manage -v**
109 |
110 | (The -v flag creates a virtual environment to isolate your project's dependencies.)
111 |
112 | **Create Both (Virtual Environment & Flask project with a predefined folder structure and blueprint setup)**
113 |
114 | `Bash or Terminal`
115 |
116 | **flask-manage -v create-app my_demo_app**
117 |
118 | # Requirements to Intall :
119 | | Requirements |
120 | | ------------- |
121 | |alembic==1.13.1
122 | Babel==2.15.0
123 | bcrypt==4.1.3
124 | blinker==1.8.2
125 | cachelib==0.9.0
126 | click==8.1.7
127 | colorama==0.4.6
128 | cssmin==0.2.0
129 | Deprecated==1.2.14
130 | Flask==3.1.0
131 | Flask-Assets==2.1.0
132 | flask-babel==4.0.0
133 | Flask-Bcrypt==1.0.1
134 | Flask-Caching==2.3.0
135 | Flask-Limiter==3.6.0
136 | Flask-Login==0.6.3
137 | Flask-Migrate==4.0.7
138 | Flask-Session==0.8.0
139 | Flask-SQLAlchemy==3.1.1
140 | Flask-WTF==1.2.1
141 | greenlet==3.0.3
142 | importlib_resources==6.4.0
143 | itsdangerous==2.2.0
144 | Jinja2==3.1.4
145 | limits==3.11.0
146 | Mako==1.3.3
147 | markdown-it-py==3.0.0
148 | MarkupSafe==2.1.5
149 | mdurl==0.1.2
150 | msgspec==0.18.6
151 | ordered-set==4.1.0
152 | packaging==24.0
153 | pillow==10.3.0
154 | Pygments==2.18.0
155 | pytz==2024.1
156 | rich==13.7.1
157 | SQLAlchemy==2.0.30
158 | typing_extensions==4.11.0
159 | webassets==2.0
160 | Werkzeug==3.0.3
161 | wrapt==1.16.0
162 | WTForms==3.1.2
163 |
164 |
165 |
166 | ## Note
167 | 1. You can change `my_demo_app` to any name of your choice
168 |
169 |
170 | ## Link to the github
171 |
172 | **https://github.com/Kennarttechl/flask_quickstart_generator.git**
173 |
174 |
175 | ## Screenshot
176 |
177 | | Screenshot | Name |
178 | ----------|-----
179 | | Successful Installation |  |
180 | | Creating Project |  |
181 | | Folder Structure |  |
182 | | Running wsgi/initializing |  |
183 | | Project Running Successful |  |
184 | | View Page |  |
185 | | Admin Login Page |  |
186 | | Login page(Mobile View) |  |
187 | | Login page(Theme change) |  |
188 | | Admin Dashboard |  |
189 | | Dashboard Mobile View |  |
190 | | Dashboard Mobile View(white theme) |  |
191 | | Reset Profile Page |  |
192 | | Landing Page (Dark theme) |  |
193 | | Page Note Found |  |
194 | | Not found (Mobile View) |  |
195 | | Too Many Request(Prevent by Limiter) |  |
196 | | Mobile View |  |
197 | | Server Error |  |
198 | | Mobile View |  |
199 | | File too Large to Upload |  |
200 | | Mobile View |  |
201 |
202 |
--------------------------------------------------------------------------------
/README_.md:
--------------------------------------------------------------------------------
1 | # flask_boilerplate_generator
2 |
3 | The project generates boilerplate code for Flask applications.
4 |
5 |
6 | # This project uses os and sys modules
7 |
8 | `os module is purposely use in this project for some reasons`
9 |
10 |
11 | **1. File and Directory Management:**
12 | 1. Create, delete, rename, move, and check the existence of files and directories.
13 | 2. Get file/directory attributes (size, creation time, etc.).
14 | 3. List directory contents.
15 | 4. Change the current working directory.
16 |
17 |
18 | **2. process management.**
19 | 1. Execute system commands and capture their output.
20 | 2. Get information about running processes.
21 |
22 |
23 | **3. Environment Variables:**
24 | 1. Get, set, and delete environment variables
25 |
26 |
27 | **4. Path Manipulation:**
28 | 1. Join path components to create valid file paths across different operating systems.
29 | 2. Split paths into components.
30 | 3. Expand user's home directory in paths
31 |
32 |
33 | *Functions:*
34 | 1. os.getcwd(): Get the current working directory.
35 | 2. os.listdir(path): List files and directories in a directory.
36 | 3. os.mkdir(path): Create a directory.
37 | 4. os.makedirs(path, exist_ok=True): Create directories recursively, ignoring errors if they already exist (optional exist_ok argument).
38 | 5. os.remove(path): Delete a file.
39 | 6. os.rmdir(path): Delete an empty directory.
40 | 7. os.path.join(path1, path2, ...): Join path components into a valid path.
41 | 8. os.path.exists(path): Check if a file or directory exists.
42 | 9. os.system(command): Execute a system command and wait for its completion.
43 | 10. os.environ: A dictionary containing the environment variables.
44 |
45 |
46 | `sys moduel is purposely use for some reasons`
47 |
48 | **Functions:**
49 | 1. sys.argv: A list containing the command-line arguments passed to the Python script when it's executed. The first element (sys.argv[0]) is always the script name itself, and subsequent elements are the arguments provided.
50 |
51 | 1. 1 In Python, argv is a built-in list variable that represents the arguments passed to a script when it's executed from the command line.
52 |
53 | 2. 2 Use sys.argv when your script needs to process command-line arguments (arguments passed when running the script).
54 |
55 | 2. sys.exit(exit_code=0): Exits the Python script with an optional exit code. The exit code can be used to indicate success (0) or an error condition (a non-zero value).
56 |
57 | 3. sys.getsizeof(object): Returns the approximate memory size, in bytes, of an object. This can be helpful for debugging memory usage.
58 |
59 | 4. sys.stdin: A standard input stream object, usually referring to the console from which the user can provide input.
60 |
61 | 5. sys.stdout: A standard output stream object, usually referring to the console where the program's output is displayed.
62 |
63 | 6. sys.stderr: A standard error stream object, usually referring to the console where error messages are displayed.
64 |
65 | 7. sys.version: A string containing the Python interpreter version information.
66 |
67 |
68 | # Attributes:
69 | 1. sys.platform: A string indicating the operating system platform the script is running on (e.g., 'win32', 'linux', 'darwin').
70 |
71 | 2. sys.path: A list containing the search path for modules. This list determines where Python looks for modules when you use the import statement. You can modify sys.path to add custom directories containing modules.
72 |
73 | 3. sys.maxsize: The largest integer value a variable of data type Py_ssize_t can hold. This can be useful for understanding the limits of integer calculations.
74 |
75 | 4. sys.executable: The absolute path to the Python interpreter binary that executed the current script.
76 |
77 |
78 |
--------------------------------------------------------------------------------
/documentation/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/404.png
--------------------------------------------------------------------------------
/documentation/4042.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/4042.png
--------------------------------------------------------------------------------
/documentation/Screenshot (73).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/Screenshot (73).png
--------------------------------------------------------------------------------
/documentation/eight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/eight.png
--------------------------------------------------------------------------------
/documentation/eleven.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/eleven.png
--------------------------------------------------------------------------------
/documentation/filetool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/filetool.png
--------------------------------------------------------------------------------
/documentation/five.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/five.png
--------------------------------------------------------------------------------
/documentation/four.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/four.png
--------------------------------------------------------------------------------
/documentation/fourteen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/fourteen.png
--------------------------------------------------------------------------------
/documentation/large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/large.png
--------------------------------------------------------------------------------
/documentation/many.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/many.png
--------------------------------------------------------------------------------
/documentation/nine.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/nine.png
--------------------------------------------------------------------------------
/documentation/one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/one.png
--------------------------------------------------------------------------------
/documentation/server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/server.png
--------------------------------------------------------------------------------
/documentation/serverr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/serverr.png
--------------------------------------------------------------------------------
/documentation/seven.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/seven.png
--------------------------------------------------------------------------------
/documentation/six.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/six.png
--------------------------------------------------------------------------------
/documentation/ten.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/ten.png
--------------------------------------------------------------------------------
/documentation/thirteen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/thirteen.png
--------------------------------------------------------------------------------
/documentation/three.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/three.png
--------------------------------------------------------------------------------
/documentation/tomany.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/tomany.png
--------------------------------------------------------------------------------
/documentation/twelve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/twelve.png
--------------------------------------------------------------------------------
/documentation/two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kennarttechl/flask_quickstart_generator/b3dfa72a94ff93ddf8fef630d508ce31e5b57582/documentation/two.png
--------------------------------------------------------------------------------
/flask_quickstart_generator/__init__.py:
--------------------------------------------------------------------------------
1 | from .cli import main
2 | from .model import USER_MODEL
3 | from .settings import APP_SETTINGS
4 | from .cmd_handler import CmdHandler
5 | from .env import ENVIRONMENT_VARIABLE
6 | from .js import (
7 | LOG_JS,
8 | PROFILE_AC,
9 | ERROR_PAGES,
10 | AUTO_LOGOUT_,
11 | TOO_MANY_JS,
12 | NOT_FOUND_JS,
13 | SADASHBOARD_JS,
14 | MAINTAINANCE_JS,
15 | FLASH_DOM_REMOVE,
16 | )
17 | from .css import (
18 | LOG_CSS,
19 | VIEW_CSS,
20 | FORBIDDEN,
21 | NOT_FOUND,
22 | BASE_FOOTER,
23 | BAD_REQUESTS,
24 | UNAUTHORIZED,
25 | MAINTAINANCE,
26 | FLASH_MESSAGE,
27 | SADASHBOARD_CSS,
28 | INTERNAL_SERVER,
29 | CONTENT_TOO_LARGE,
30 | TOO_MANAY_REQUEST,
31 | ACCOUNT_CSS_PROFILE
32 | )
33 | from .forms import (
34 | SEARCH_FORM,
35 | SADASHBOARD_FORM,
36 | UPLOAD_FILES_FORM,
37 | AUTHENTICATION_FORM,
38 | ACCOUNT_SETTINGS_FORM,
39 | )
40 | from .html import (
41 | BASE_HTML,
42 | VIEW_HTML,
43 | USER_ROLE_HTML,
44 | FORBIDDEN_HTML,
45 | NOT_FOUND_HTML,
46 | UNAUTHORIZED_HTML,
47 | BAD_REQUESTS_HTML,
48 | MAINTAINANCE_HTML,
49 | FLASH_CUSTOM_HTML,
50 | SADASHBOARD_SECURE,
51 | SADMIN_LOGIN_SECURE,
52 | DEMO_HTML_TEMPLATES,
53 | PROFILE_UPDATE_HTML,
54 | INTERNAL_SERVER_HTML,
55 | CONTENT_TOO_LARGE_HTML,
56 | TOO_MANAY_REQUEST_HTML,
57 | ACCOUNT_SETTING_FORM_HTML,
58 | AUTHENTICATION_LOGIN_HTML,
59 | AUTHENTICATION_REGISTER_HTML,
60 | )
61 | from .routes_ import (
62 | GITIGNORE,
63 | APP_STARTUP,
64 | ANSI_COLORS_,
65 | ACCOUNT_UTILS,
66 | CACHING_CONSTANT,
67 | VIEW_TEMPLATE_CODE,
68 | ADMIN_TEMPLATE_CODE,
69 | SEARCH_TEMPLATE_CODE,
70 | SUPER_ADMIN_DASHBOARD_,
71 | FILES_UPLOAD_TEMPLATE_CODE,
72 | ERROR_HANDLER_TEMPLATE_CODE,
73 | AUTHENTICATION_TEMPLATE_CODE,
74 | ACCOUNT_SETTINGS_TEMPLATE_CODE,
75 | )
76 |
--------------------------------------------------------------------------------
/flask_quickstart_generator/cli.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from colorama import Fore, Style
3 | from .cmd_handler import CmdHandler
4 |
5 |
6 | docs = """
7 | Flask Quickstart Generator:
8 | The Flask Quickstart Generator is a command-line tool (CLI) built with Python and the Flask Framework. It provides Developers with a convenient way to generate the folder structure and code for new Flask applications. This simplifies the process of starting new projects, allowing developers to focus on building application logic instead of skipping the boilerplate setup.
9 |
10 | ***** User Commands: To Get Started Quickly *****
11 |
12 | command for creating the project folder structure `Only`:
13 | flask-manage create-app my_demo_app
14 |
15 | command for creating virtualenv venv `Only`:
16 | flask-manage -v
17 |
18 | commands for creating Both (Virtual Environment & App or Project Structure) :
19 | flask-manage -v create-app my_demo_app
20 |
21 |
22 | ==== Note =====
23 | You can change `my_demo_app` to any name of your choice (Depending on your project)
24 | """
25 |
26 |
27 | def main():
28 | """This function is the entry point for the script's execution."""
29 |
30 | command = sys.argv[1].strip() if len(sys.argv) > 1 else ""
31 | argument = sys.argv[2].strip() if len(sys.argv) > 2 else ""
32 | project_name = sys.argv[3].strip() if len(sys.argv) > 3 else ""
33 |
34 | if command == "-v" and argument == "create-app" and project_name != "":
35 | CmdHandler.init()
36 | CmdHandler.generate_flask_app_folder(project_name)
37 |
38 | elif command == "-v" and argument == "":
39 | CmdHandler.init()
40 | # print(f"{GREEN}Virtual environment created successfully{RESET}")
41 |
42 | elif command == "create-app" and argument != "":
43 | CmdHandler.generate_flask_app_folder(argument)
44 | print(f"{Fore.GREEN}***** Project created successfully *****{Style.RESET_ALL}")
45 | print('')
46 | print(f"{Fore.GREEN}***** Happy Coding *****{Style.RESET_ALL}")
47 |
48 | else:
49 | sys.exit(f"{Fore.YELLOW}{docs}{Style.RESET_ALL}")
50 |
51 | if __name__ == "__main__":
52 | main()
53 |
--------------------------------------------------------------------------------
/flask_quickstart_generator/cmd_handler.py:
--------------------------------------------------------------------------------
1 | import os
2 | from .model import USER_MODEL
3 | from colorama import Fore, Style
4 | from .settings import APP_SETTINGS
5 | from .env import ENVIRONMENT_VARIABLE
6 | from .js import (
7 | LOG_JS,
8 | PROFILE_AC,
9 | ERROR_PAGES,
10 | TOO_MANY_JS,
11 | NOT_FOUND_JS,
12 | AUTO_LOGOUT_,
13 | MAINTAINANCE_JS,
14 | SADASHBOARD_JS,
15 | FLASH_DOM_REMOVE,
16 | )
17 | from .css import (
18 | LOG_CSS,
19 | VIEW_CSS,
20 | FORBIDDEN,
21 | NOT_FOUND,
22 | BASE_FOOTER,
23 | BAD_REQUESTS,
24 | UNAUTHORIZED,
25 | MAINTAINANCE,
26 | FLASH_MESSAGE,
27 | SADASHBOARD_CSS,
28 | INTERNAL_SERVER,
29 | CONTENT_TOO_LARGE,
30 | TOO_MANAY_REQUEST,
31 | ACCOUNT_CSS_PROFILE,
32 | )
33 | from .forms import (
34 | SEARCH_FORM,
35 | SADASHBOARD_FORM,
36 | UPLOAD_FILES_FORM,
37 | AUTHENTICATION_FORM,
38 | ACCOUNT_SETTINGS_FORM,
39 | )
40 |
41 | from .html import (
42 | BASE_HTML,
43 | VIEW_HTML,
44 | USER_ROLE_HTML,
45 | FORBIDDEN_HTML,
46 | NOT_FOUND_HTML,
47 | UNAUTHORIZED_HTML,
48 | BAD_REQUESTS_HTML,
49 | MAINTAINANCE_HTML,
50 | FLASH_CUSTOM_HTML,
51 | SADASHBOARD_SECURE,
52 | SADMIN_LOGIN_SECURE,
53 | DEMO_HTML_TEMPLATES,
54 | PROFILE_UPDATE_HTML,
55 | INTERNAL_SERVER_HTML,
56 | CONTENT_TOO_LARGE_HTML,
57 | TOO_MANAY_REQUEST_HTML,
58 | ACCOUNT_SETTING_FORM_HTML,
59 | AUTHENTICATION_LOGIN_HTML,
60 | AUTHENTICATION_REGISTER_HTML,
61 | )
62 |
63 | from .routes_ import (
64 | GITIGNORE,
65 | APP_STARTUP,
66 | ANSI_COLORS_,
67 | ACCOUNT_UTILS,
68 | CACHING_CONSTANT,
69 | VIEW_TEMPLATE_CODE,
70 | ADMIN_TEMPLATE_CODE,
71 | SEARCH_TEMPLATE_CODE,
72 | SUPER_ADMIN_DASHBOARD_,
73 | FILES_UPLOAD_TEMPLATE_CODE,
74 | ERROR_HANDLER_TEMPLATE_CODE,
75 | AUTHENTICATION_TEMPLATE_CODE,
76 | ACCOUNT_SETTINGS_TEMPLATE_CODE,
77 | )
78 |
79 | APPLICATION_STRUCTURE = {
80 | "templates": {"base.html": BASE_HTML, "message.html": FLASH_MESSAGE},
81 | "views": {"routes.py": VIEW_TEMPLATE_CODE, "__init__.py": ""},
82 | "errors": {
83 | "routes.py": ERROR_HANDLER_TEMPLATE_CODE,
84 | "__init__.py": "",
85 | },
86 | "authentication": {
87 | "routes.py": AUTHENTICATION_TEMPLATE_CODE,
88 | "form.py": AUTHENTICATION_FORM,
89 | "__init__.py": "",
90 | "templates": {
91 | "login.html": AUTHENTICATION_LOGIN_HTML,
92 | "signup.html": AUTHENTICATION_REGISTER_HTML,
93 | },
94 | },
95 | "super_admin": {
96 | "routes.py": SUPER_ADMIN_DASHBOARD_,
97 | "form.py": SADASHBOARD_FORM,
98 | "__init__.py": "",
99 | "templates": {
100 | "salogin_secure.html": SADMIN_LOGIN_SECURE,
101 | "sadashboard_secure.html": SADASHBOARD_SECURE,
102 | "flash_message.html": FLASH_CUSTOM_HTML,
103 | },
104 | },
105 | "admin": {"routes.py": ADMIN_TEMPLATE_CODE, "form.py": "", "__init__.py": ""},
106 | "search": {
107 | "routes.py": SEARCH_TEMPLATE_CODE,
108 | "form.py": SEARCH_FORM,
109 | "__init__.py": "",
110 | },
111 | "static": {
112 | "css": None,
113 | "js": None,
114 | "fonts": None,
115 | "icons": "flask_cli.png",
116 | "media": "default.jpg",
117 | },
118 | "database": {"models.py": USER_MODEL, "__init__.py": ""},
119 | "account_settings": {
120 | "routes.py": ACCOUNT_SETTINGS_TEMPLATE_CODE,
121 | "form.py": ACCOUNT_SETTINGS_FORM,
122 | "__init__.py": "",
123 | },
124 | "media_utils": {"utils.py": ACCOUNT_UTILS, "__init__.py": ""},
125 | "uploads": {
126 | "routes.py": FILES_UPLOAD_TEMPLATE_CODE,
127 | "form.py": UPLOAD_FILES_FORM,
128 | "__init__.py": "",
129 | },
130 | "caching": {"cache_constant.py": CACHING_CONSTANT, "__init__.py": ""},
131 | "config": {".env": ENVIRONMENT_VARIABLE, ".flaskenv": ""},
132 | }
133 |
134 |
135 | class CmdHandler:
136 | def init():
137 | print(
138 | f"{Fore.GREEN}Please wait, app is setting up virtual environment....{Style.RESET_ALL}"
139 | )
140 |
141 | os.system("pip install virtualenv")
142 |
143 | os.system("python -m venv venv")
144 |
145 | print(
146 | f"{Fore.GREEN}***** Successfully created virtual environment *****{Style.RESET_ALL}"
147 | )
148 |
149 | print("")
150 |
151 | print(
152 | f"{Fore.YELLOW}Please wait installing collected packages: Flask, python-dotenv, Flask-Session, Flask-Limiter, flask-babel, Flask-Caching, Flask-SQLAlchemy, psycopg2, Flask-Migrate, Flask-WTF, WTForms, waitress, pillow, Flask-Bcrypt, Flask-Login {Style.RESET_ALL}"
153 | )
154 |
155 | os.system(
156 | "pip install Flask flask-babel python-dotenv Flask-Bcrypt waitress Flask-Caching Flask-Limiter Flask-Login Flask-Migrate Flask-Session Flask-SQLAlchemy Flask-WTF WTForms psycopg2 pillow Flask-Login"
157 | )
158 |
159 | print(
160 | f"{Fore.GREEN}These packages are installed globally on your computer. To use them, activate your virtual environment and reinstall them inside.{Style.RESET_ALL}"
161 | )
162 |
163 | print("")
164 |
165 | print(
166 | f"{Fore.RED}To activate the virtual environment, navigate into the 'venv' directory and run 'Scripts/activate' on Windows or 'source bin/activate' on Unix-based systems.{Style.RESET_ALL}"
167 | )
168 | print("")
169 |
170 | print(f"{Fore.GREEN}***** Project created successfully *****{Style.RESET_ALL}")
171 |
172 | print("")
173 |
174 | @staticmethod
175 | def generate_flask_app_folder(app_folder_name):
176 | try:
177 | if not os.path.exists(app_folder_name):
178 | os.mkdir(app_folder_name)
179 |
180 | # Create initial files: wsgi.py, __init__.py, .gitignore
181 | CmdHandler.create_file(
182 | "wsgi.py", APP_STARTUP.replace("my_demo_app", app_folder_name)
183 | )
184 | CmdHandler.create_file(
185 | os.path.join(app_folder_name, "__init__.py"),
186 | APP_SETTINGS.replace("my_demo_app", app_folder_name),
187 | )
188 | CmdHandler.create_file(
189 | os.path.join(app_folder_name, ".gitignore"), GITIGNORE
190 | )
191 | CmdHandler.create_file(
192 | os.path.join(app_folder_name, "ansi_.py"), ANSI_COLORS_
193 | )
194 |
195 | for dir_name, content in APPLICATION_STRUCTURE.items():
196 | os.makedirs(os.path.join(app_folder_name, dir_name), exist_ok=True)
197 |
198 | # Create templates for specific directories
199 | if dir_name in [
200 | "uploads",
201 | "errors",
202 | "views",
203 | "admin",
204 | "search",
205 | "super_admin",
206 | "authentication",
207 | "account_settings",
208 | ]:
209 | template_folder = os.path.join(
210 | app_folder_name, dir_name, "templates"
211 | )
212 | os.makedirs(template_folder, exist_ok=True)
213 |
214 | if dir_name == "errors":
215 | CmdHandler.create_error_templates(template_folder)
216 | elif dir_name == "authentication":
217 | CmdHandler.create_auth_templates(
218 | template_folder, content["templates"]
219 | )
220 | elif dir_name == "super_admin":
221 | CmdHandler.create_super_admin_templates(
222 | template_folder, content["templates"]
223 | )
224 | else:
225 | CmdHandler.create_templates_for_directories(
226 | template_folder, dir_name
227 | )
228 |
229 | # Create base template for the "templates" folder
230 | if dir_name == "templates":
231 | CmdHandler.create_templates_base(
232 | os.path.join(app_folder_name, "templates")
233 | )
234 |
235 | # Handle static files
236 | if dir_name == "static":
237 | CmdHandler.create_static_files(
238 | app_folder_name, dir_name, content
239 | )
240 |
241 | # Call to create CSS files within the static/css folder
242 | CmdHandler.create_css_files(app_folder_name)
243 |
244 | # Write other files in the directory
245 | if dir_name in [
246 | "views",
247 | "admin",
248 | "errors",
249 | "config",
250 | "search",
251 | "caching",
252 | "uploads",
253 | "database",
254 | "super_admin",
255 | "media_utils",
256 | "authentication",
257 | "account_settings",
258 | ]:
259 | for filename, file_content in content.items():
260 | if filename != "templates":
261 | CmdHandler.create_file(
262 | os.path.join(app_folder_name, dir_name, filename),
263 | file_content.replace(
264 | "my_demo_app", app_folder_name
265 | ),
266 | )
267 |
268 | else:
269 | print(
270 | f"{Fore.YELLOW} The Folder {app_folder_name} already exists. {Style.RESET_ALL}"
271 | )
272 | except Exception as e:
273 | print(f"{Fore.RED} Error: {e}{Style.RESET_ALL}")
274 |
275 | @staticmethod
276 | def create_templates_base(template_folder):
277 | """Creates a base HTML file in the 'templates' directory."""
278 | base_html_path = os.path.join(template_folder, "base.html")
279 | CmdHandler.create_file(base_html_path, BASE_HTML)
280 |
281 | @staticmethod
282 | def create_file(file_path, content):
283 | """Helper function to create a file with given content."""
284 | with open(file_path, "w") as file:
285 | file.write(content)
286 |
287 | @staticmethod
288 | def create_css_files(app_folder_name):
289 | """Create CSS files like sadashboard.css, log.css, flash.css."""
290 | css_dir = os.path.join(app_folder_name, "static", "css")
291 | os.makedirs(css_dir, exist_ok=True) # Ensure the css directory is created
292 |
293 | # Write each CSS constant to its respective file
294 | css_files = {
295 | "403.css": FORBIDDEN,
296 | "404.css": NOT_FOUND,
297 | "503.css": MAINTAINANCE,
298 | "400.css": BAD_REQUESTS,
299 | "401.css": UNAUTHORIZED,
300 | "500.css": INTERNAL_SERVER,
301 | "413.css": CONTENT_TOO_LARGE,
302 | "429.css": TOO_MANAY_REQUEST,
303 | "log.css": LOG_CSS,
304 | "view.css": VIEW_CSS,
305 | "footer.css": BASE_FOOTER,
306 | "flash.css": FLASH_MESSAGE,
307 | "sadashboard.css": SADASHBOARD_CSS,
308 | "account.css": ACCOUNT_CSS_PROFILE,
309 | }
310 |
311 | for filename, css_content in css_files.items():
312 | file_path = os.path.join(css_dir, filename)
313 | CmdHandler.create_file(file_path, css_content)
314 |
315 | @staticmethod
316 | def create_error_templates(template_folder):
317 | error_files_content = {
318 | "bad_request.html": BAD_REQUESTS_HTML,
319 | "forbidden.html": FORBIDDEN_HTML,
320 | "not_found.html": NOT_FOUND_HTML,
321 | "maintenance.html": MAINTAINANCE_HTML,
322 | "payload_data.html": CONTENT_TOO_LARGE_HTML,
323 | "invalid_path.html": "",
324 | "internal_server.html": INTERNAL_SERVER_HTML,
325 | "too_many_requests.html": TOO_MANAY_REQUEST_HTML,
326 | "unauthorized.html": UNAUTHORIZED_HTML,
327 | }
328 | # Loop through the error files and create each with the corresponding content
329 | for error_file, html_content in error_files_content.items():
330 | file_path = os.path.join(template_folder, error_file)
331 | CmdHandler.create_file(file_path, html_content)
332 |
333 | @staticmethod
334 | def create_auth_templates(template_folder, auth_templates):
335 | """Create authentication templates like login and signup."""
336 | for filename, html_content in auth_templates.items():
337 | file_path = os.path.join(template_folder, filename)
338 | CmdHandler.create_file(file_path, html_content)
339 |
340 | @staticmethod
341 | def create_super_admin_templates(template_folder, super_admin_templates):
342 | """Create super admin templates using SADMIN_LOGIN_SECURE and SADASHBOARD_SECURE."""
343 | CmdHandler.create_file(
344 | os.path.join(template_folder, "salogin_secure.html"), SADMIN_LOGIN_SECURE
345 | )
346 | CmdHandler.create_file(
347 | os.path.join(template_folder, "sadashboard_secure.html"), SADASHBOARD_SECURE
348 | )
349 | CmdHandler.create_file(
350 | os.path.join(template_folder, "flash_message.html"), FLASH_CUSTOM_HTML
351 | )
352 | CmdHandler.create_file(
353 | os.path.join(template_folder, "flash_message.html"), FLASH_CUSTOM_HTML
354 | )
355 | CmdHandler.create_file(
356 | os.path.join(template_folder, "account_edit_data.html"), PROFILE_UPDATE_HTML
357 | )
358 | CmdHandler.create_file(
359 | os.path.join(template_folder, "user_role.html"), USER_ROLE_HTML
360 | )
361 |
362 | @staticmethod
363 | def create_templates_for_directories(template_folder, dir_name):
364 | """Create general templates for directories like views, admin, search."""
365 | template_filenames = {
366 | "views": "index.html",
367 | "admin": "controller.html",
368 | "search": "item_search.html",
369 | "uploads": "file_upload.html",
370 | "account_settings": "update_account.html", # Fix incorrect variable reference
371 | }
372 |
373 | file_name = template_filenames.get(dir_name, None)
374 |
375 | if file_name:
376 | if dir_name == "views":
377 | content = VIEW_HTML # Use VIEW_HTML for index.html
378 | elif dir_name == "account_settings":
379 | content = ACCOUNT_SETTING_FORM_HTML
380 | else:
381 | content = DEMO_HTML_TEMPLATES # Keep default content for other templates
382 |
383 | CmdHandler.create_file(os.path.join(template_folder, file_name), content)
384 |
385 |
386 | # @staticmethod
387 | # def create_templates_for_directories(template_folder, dir_name):
388 | # """Create general templates for directories like views, admin, search."""
389 | # template_filenames = {
390 | # "views": "index.html",
391 | # "admin": "controller.html",
392 | # "search": "item_search.html",
393 | # "uploads": "file_upload.html",
394 | # "account_settings": "update_account.html", # Fix incorrect variable reference
395 | # }
396 |
397 | # file_name = template_filenames.get(dir_name, None)
398 |
399 | # if file_name:
400 | # content = (
401 | # ACCOUNT_SETTING_FORM_HTML
402 | # if dir_name == "account_settings"
403 | # else DEMO_HTML_TEMPLATES
404 | # )
405 | # CmdHandler.create_file(os.path.join(template_folder, file_name), content)
406 |
407 | @staticmethod
408 | def create_static_files(app_folder_name, static_dir, content):
409 | """Create static files like CSS, JS, fonts, etc."""
410 | for static_subdir, value in content.items():
411 | static_subdir_path = os.path.join(
412 | app_folder_name, static_dir, static_subdir
413 | )
414 | os.makedirs(
415 | static_subdir_path, exist_ok=True
416 | ) # Ensure the static subdirectories are created
417 |
418 | # Handle JavaScript files specifically in the js folder
419 | if static_subdir == "js":
420 | js_files = {
421 | "log.js": LOG_JS,
422 | "account.js": PROFILE_AC,
423 | "too_many.js": TOO_MANY_JS,
424 | "notfound.js": NOT_FOUND_JS,
425 | "auto_logout.js": AUTO_LOGOUT_,
426 | "dashboard.js": SADASHBOARD_JS,
427 | "error_pages_all.js": ERROR_PAGES,
428 | "maintainance.js": MAINTAINANCE_JS,
429 | "flash_remove_dom.js": FLASH_DOM_REMOVE,
430 | }
431 |
432 | for filename, js_content in js_files.items():
433 | js_file_path = os.path.join(static_subdir_path, filename)
434 | CmdHandler.create_file(js_file_path, js_content)
435 |
436 | # Handle font files in static/fonts directory
437 | if static_subdir == "fonts":
438 | font_files = [
439 | "EBGaramond-Medium.woff",
440 | "EBGaramond-Medium.woff2",
441 | ]
442 | for font_file in font_files:
443 | font_file_path = os.path.join(static_subdir_path, font_file)
444 | CmdHandler.create_file(font_file_path, "") #Creates an empty file for now
445 |
446 | # Only create files if 'value' is not empty (avoid creating empty files)
447 | if value and static_subdir not in ["js", "fonts"]: # We handle JS and fonts separately
448 | static_file_path = os.path.join(static_subdir_path, value)
449 | CmdHandler.create_file(static_file_path, "") # Create empty files for now
450 |
451 | # # Only create files if 'value' is not empty (avoid creating empty files)
452 | # if value and static_subdir != "js": # We handle JS separately, so skip here
453 | # static_file_path = os.path.join(static_subdir_path, value)
454 | # CmdHandler.create_file(
455 | # static_file_path, ""
456 | # ) # Create empty files for now
457 |
--------------------------------------------------------------------------------
/flask_quickstart_generator/css.py:
--------------------------------------------------------------------------------
1 | LOG_CSS =\
2 | """
3 |
4 | * {
5 | margin: 0;
6 | padding: 0;
7 | box-sizing: border-box;
8 | }
9 | /* Montserrat */
10 | body,
11 | html {
12 | height: 100%;
13 | font-family: Arial, sans-serif;
14 | }
15 |
16 | h2 {
17 | font-size: 35px;
18 | }
19 |
20 | .container-fluid {
21 | display: flex;
22 | height: 100vh;
23 | padding: 0;
24 | }
25 |
26 | .left {
27 | flex: 1;
28 | background: url("https://images.pexels.com/photos/450035/pexels-photo-450035.jpeg")
29 | no-repeat center center;
30 | background-size: cover;
31 | min-height: 100vh;
32 | height: auto;
33 | }
34 |
35 | .right {
36 | flex: 1;
37 | display: flex;
38 | justify-content: center;
39 | align-items: center;
40 | background-color: #f9f9f9;
41 | flex-direction: column;
42 | text-align: center;
43 | padding: 3px;
44 | }
45 |
46 | .right form {
47 | width: 80%;
48 | max-width: 400px;
49 | margin: 0;
50 | }
51 |
52 | input {
53 | margin-bottom: 15px; /* Adjust the space between the input field*/
54 | width: 100%;
55 | min-height: 2.6rem; /* Adjust the input field height*/
56 | }
57 |
58 | @media screen and (max-width: 1024px) {
59 | .container-fluid {
60 | flex-direction: column;
61 | }
62 |
63 | .left,
64 | .right {
65 | flex: 1 1 50%;
66 | }
67 | }
68 |
69 | @media only screen and (min-width: 768px) and (max-width: 810px) {
70 | .container-fluid {
71 | flex-direction: column;
72 | }
73 |
74 | .left {
75 | height: 40vh;
76 | background-position: top;
77 | order: 1;
78 | }
79 |
80 | .right {
81 | height: auto;
82 | padding: 100px;
83 | justify-content: flex-start;
84 | align-items: center;
85 | min-height: 80vh;
86 | }
87 |
88 | .right form {
89 | width: 150%;
90 | }
91 | }
92 |
93 | @media screen and (max-width: 375px) {
94 | .container-fluid {
95 | flex-direction: column;
96 | }
97 |
98 | .left {
99 | min-height: 50vh;
100 | background-position: center;
101 | order: 1;
102 | }
103 |
104 | .right {
105 | justify-content: center;
106 | align-items: center;
107 | padding-top: 2rem;
108 | min-height: 60vh;
109 | }
110 |
111 | .right h2 {
112 | font-size: 20px;
113 | }
114 |
115 | .right form {
116 | width: 95%;
117 | }
118 |
119 | input {
120 | min-height: 2.5rem;
121 | }
122 | }
123 |
124 | /* Dark Mode styles */
125 | .dark-mode {
126 | /* background: #242435; */
127 | background-color: #2f3d40;
128 | color: white;
129 | }
130 |
131 | .dark-mode .right {
132 | background-color: #2f3d40;
133 | /* background: #242435; */
134 | color: white;
135 | }
136 |
137 | .dark-mode h2,
138 | .dark-mode p {
139 | color: white;
140 | }
141 |
142 | /* Toggle Switch */
143 | .toggle-switch {
144 | position: fixed;
145 | top: 30px;
146 | right: 30px;
147 | display: flex;
148 | align-items: center;
149 | cursor: pointer;
150 | z-index: 1000;
151 | }
152 |
153 | .toggle-switch input {
154 | display: none;
155 | }
156 |
157 | .slider {
158 | width: 50px;
159 | height: 25px;
160 | background: #44f378;
161 | border-radius: 25px;
162 | position: relative;
163 | transition: background 0.3s;
164 | }
165 |
166 | .slider:before {
167 | content: "";
168 | position: absolute;
169 | width: 20px;
170 | height: 20px;
171 | background: white;
172 | border-radius: 50%;
173 | top: 50%;
174 | left: 4px;
175 | transform: translateY(-50%);
176 | transition: left 0.3s;
177 | }
178 |
179 | input:checked + .slider {
180 | background: #007bff;
181 | }
182 |
183 | input:checked + .slider:before {
184 | left: 26px;
185 | }
186 | """
187 |
188 | SADASHBOARD_CSS =\
189 | """
190 | * {
191 | margin: 0;
192 | padding: 0;
193 | box-sizing: border-box;
194 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
195 | }
196 |
197 | body {
198 | background-color: #f5f9f9;
199 | color: #333;
200 | display: flex;
201 | flex-direction: column;
202 | min-height: 100vh;
203 | transition: background-color 0.3s, color 0.3s;
204 | }
205 |
206 | /* Top bar & sidebar toggle section (fixed full width) */
207 | .topbar {
208 | position: fixed;
209 | top: 0;
210 | left: 0;
211 | width: 100%;
212 | height: 60px;
213 | background-color: #ffffff;
214 | border-bottom: 1px solid #e0e0e0;
215 | display: flex;
216 | align-items: center;
217 | justify-content: space-between;
218 | padding: 0 1.5rem;
219 | z-index: 200;
220 | transition: background-color 0.3s, border-color 0.3s;
221 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
222 | }
223 |
224 | .topbar-left {
225 | display: flex;
226 | align-items: center;
227 | gap: 1rem;
228 | }
229 |
230 | .toggle-sidebar {
231 | cursor: pointer;
232 | font-size: 1.2rem;
233 | color: #999;
234 | transition: color 0.3s;
235 | }
236 |
237 | .toggle-sidebar:hover {
238 | color: #666;
239 | }
240 | /* End top bar & Sidebar toggle */
241 |
242 | /* Top bar search input section */
243 | .input-group {
244 | margin-bottom: 2px;
245 | }
246 |
247 | .input-group .form-control {
248 | width: 100%;
249 | max-width: 192px;
250 | }
251 |
252 | .input-group form {
253 | display: flex;
254 | }
255 |
256 | .input-group input {
257 | flex: 1;
258 | width: 2px;
259 | margin-right: 5px;
260 | }
261 |
262 | .input-group button {
263 | width: 40px;
264 | padding: 0;
265 | display: flex;
266 | color: #ffffff;
267 | justify-content: center;
268 | align-items: center;
269 | }
270 | /* End top bar search input section */
271 |
272 | /* Table search section */
273 | .search-container {
274 | margin-bottom: 20px;
275 | }
276 |
277 | .search-container .form-control {
278 | width: 100%;
279 | max-width: 300px;
280 | }
281 |
282 | .search-container form {
283 | display: flex;
284 | }
285 |
286 | .search-container input {
287 | flex: 1;
288 | margin-right: 5px;
289 | }
290 | /* End table search section */
291 |
292 | /* Top bar user profile & dropdown menu section */
293 | .user-profile {
294 | display: flex;
295 | align-items: center;
296 | position: relative;
297 | cursor: pointer;
298 | gap: 0.5rem;
299 | }
300 |
301 | .user-profile img {
302 | width: 35px;
303 | height: 35px;
304 | border-radius: 50%;
305 | object-fit: cover;
306 | }
307 |
308 | .user-profile span {
309 | font-weight: 500;
310 | color: #333;
311 | transition: color 0.3s;
312 | }
313 |
314 | .user-profile i {
315 | color: #888;
316 | transition: transform 0.3s;
317 | }
318 |
319 | .dropdown-menu {
320 | position: absolute;
321 | top: 130%;
322 | right: 0;
323 | background-color: #fff;
324 | border: 1px solid #e0e0e0;
325 | border-radius: 5px;
326 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
327 | display: none;
328 | min-width: 150px;
329 | z-index: 99;
330 | transition: background-color 0.3s, border-color 0.3s;
331 | }
332 |
333 | .dropdown-menu ul {
334 | list-style: none;
335 | margin: 0;
336 | padding: 0.5rem 0;
337 | }
338 |
339 | .dropdown-menu ul li {
340 | padding: 0.5rem 1rem;
341 | }
342 |
343 | .dropdown-menu ul li a {
344 | color: #333;
345 | text-decoration: none;
346 | display: block;
347 | transition: background 0.3s, color 0.3s;
348 | }
349 |
350 | .dropdown-menu ul li:hover {
351 | background-color: #f0f0f0;
352 | }
353 |
354 | .user-profile.open .dropdown-menu {
355 | display: block;
356 | }
357 |
358 | .user-profile.open i {
359 | transform: rotate(180deg);
360 | }
361 |
362 | .user-profile i {
363 | transform: none !important;
364 | -webkit-transform: none !important;
365 | }
366 | /* End top bar user profile & dropdown menu section */
367 |
368 | /* Main section & sidebar section */
369 | .main-section {
370 | flex: 1;
371 | display: flex;
372 | transition: margin-left 0.3s ease;
373 | margin-top: 60px;
374 | margin-left: 250px;
375 | }
376 |
377 | .main-section.sidebar-closed {
378 | margin-left: 0;
379 | }
380 |
381 | /* Sidebar section */
382 | .sidebar {
383 | position: fixed;
384 | top: 60px;
385 | left: 0;
386 | bottom: 0;
387 | width: 250px;
388 | background-color: #ffffff;
389 | border-right: 1px solid #e0e0e0;
390 | padding: 1rem;
391 | overflow-y: auto;
392 | transition: transform 0.3s ease, background-color 0.3s, border-color 0.3s;
393 | z-index: 150;
394 | }
395 |
396 | /* Override Bootstrap for Sidebar */
397 | .sidebar .nav-menu {
398 | margin: 0 !important;
399 | padding: 0 !important;
400 | list-style: none !important;
401 | }
402 |
403 | .sidebar .nav-menu li {
404 | margin: 0 !important;
405 | padding: 0 !important;
406 | }
407 |
408 | @font-face {
409 | font-family: "tech";
410 | src: url("../fonts/EBGaramond-Medium.woff") format("woff2"),
411 | url("../fonts/EBGaramond-Medium.woff") format("woff");
412 | font-style: normal;
413 | }
414 |
415 | .sidebar .nav-menu li a {
416 | display: flex;
417 | align-items: center;
418 | text-decoration: none;
419 | color: #333;
420 | padding: 0.5rem 0.75rem;
421 | border-radius: 5px;
422 | transition: background-color 0.3s;
423 | transition: left 0.7s ease;
424 | font-family: tech;
425 | font-size: 1.2rem;
426 | letter-spacing: 0.2px;
427 | }
428 |
429 | .sidebar.closed {
430 | transform: translateX(-100%);
431 | }
432 |
433 | .logo {
434 | font-size: 1.1rem;
435 | font-weight: 600;
436 | color: #0fb690;
437 | margin-bottom: 1rem;
438 | display: flex;
439 | align-items: center;
440 | transition: color 0.3s;
441 | }
442 |
443 | .logo i {
444 | margin-right: 0.5rem;
445 | }
446 |
447 | .nav-menu {
448 | list-style: none;
449 | margin-top: 1rem;
450 | }
451 |
452 | .nav-menu li {
453 | margin-bottom: 0.5rem;
454 | }
455 |
456 | .nav-menu li a {
457 | display: flex;
458 | align-items: center;
459 | text-decoration: none;
460 | color: #333;
461 | padding: 0.5rem 0.75rem;
462 | border-radius: 5px;
463 | transition: background-color 0.3s, color 0.3s;
464 | }
465 |
466 | .nav-menu li a:hover {
467 | background-color: #f0f0f0;
468 | }
469 |
470 | .nav-menu li a.active {
471 | background-color: #dff0eb;
472 | color: #0fb690;
473 | font-weight: 500;
474 | }
475 |
476 | .nav-menu li a i {
477 | margin-right: 0.5rem;
478 | }
479 |
480 | /* Sidebar submenu style section*/
481 | .has-submenu > a {
482 | position: relative;
483 | justify-content: space-between;
484 | align-items: center;
485 | }
486 | .has-submenu > a .arrow {
487 | font-size: 0.8rem;
488 | margin-left: auto;
489 | transition: transform 0.3s;
490 | }
491 | .submenu {
492 | display: none;
493 | margin-left: 1.5rem;
494 | }
495 | .submenu li a {
496 | font-size: 0.9rem;
497 | }
498 | .has-submenu.open .submenu {
499 | display: block;
500 | }
501 | .has-submenu.open .arrow {
502 | transform: rotate(90deg);
503 | }
504 | /* End Main section & sidebar section */
505 |
506 | /* Main content section */
507 | .main-content {
508 | flex: 1;
509 | display: flex;
510 | flex-direction: column;
511 | padding: 1rem;
512 | background-color: #d5e7e2;
513 | overflow-y: auto;
514 | transition: background-color 0.3s, color 0.3s;
515 | }
516 | /* End main content section */
517 |
518 | /* Content head section */
519 | .content-header {
520 | padding: 1.5rem 1rem;
521 | display: flex;
522 | justify-content: space-between;
523 | align-items: center;
524 | flex-wrap: wrap;
525 | transition: color 0.3s;
526 | }
527 | .content-header h1 {
528 | font-size: 1.25rem;
529 | font-weight: 600;
530 | margin-bottom: 0.5rem;
531 | }
532 | .content-header .invoice-meta {
533 | font-size: 0.9rem;
534 | color: #666;
535 | }
536 | .invoice-info {
537 | text-align: right;
538 | }
539 | .invoice-info p {
540 | margin: 0.25rem 0;
541 | }
542 | /* End content head section */
543 |
544 | /* Dashboard Cards*/
545 | .dashboard-container {
546 | display: grid;
547 | margin-bottom: 25px;
548 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
549 | gap: 20px;
550 | }
551 |
552 | .dashboard-box {
553 | padding: 20px;
554 | border-radius: 8px;
555 | color: #fff;
556 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
557 | transition: all 0.3s ease;
558 | }
559 |
560 | .dashboard-box:nth-child(1) {
561 | background-color: #53736a; /* Light Blue */
562 | }
563 |
564 | .dashboard-box:nth-child(2) {
565 | background-color: #37b24d; /* Green */
566 | }
567 |
568 | .dashboard-box:nth-child(3) {
569 | background-color: #3d3d3d; /* Orange */
570 | }
571 |
572 | .dashboard-box:nth-child(4) {
573 | background-color: #506266; /* Red */
574 | }
575 |
576 | .dashboard-box:hover {
577 | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
578 | }
579 |
580 | .dashboard-box h2 {
581 | font-size: 1.2rem;
582 | margin-bottom: 10px;
583 | }
584 |
585 | .dashboard-box p {
586 | font-size: 1.5rem;
587 | font-weight: bold;
588 | margin: 5px 0;
589 | }
590 |
591 | .dashboard-box .subtext {
592 | font-size: 0.9rem;
593 | opacity: 0.8;
594 | }
595 | /* End dashboard cards*/
596 |
597 | /* Table container section */
598 | .table-container {
599 | padding: 1rem;
600 | background-color: #fff;
601 | border-radius: 6px;
602 | transition: background-color 0.3s, color 0.3s;
603 | }
604 |
605 | /* Light white shadow for the table */
606 | .table-light-shadow {
607 | box-shadow: 0 4px 8px rgba(255, 255, 255, 0.2);
608 | }
609 |
610 | h3 {
611 | font-size: 18px;
612 | color: #ec530c;
613 | }
614 |
615 | h1 {
616 | font-size: 18px;
617 | color: #ec530c !important;
618 | }
619 |
620 | .main--title h4 {
621 | font-size: 20px;
622 | color: #0e0000;
623 | }
624 |
625 | .table-container {
626 | width: 100%;
627 | overflow-x: auto;
628 | }
629 |
630 | .table-container {
631 | width: 100%;
632 | overflow-x: auto;
633 | background-color: white;
634 | padding: 20px;
635 | margin-bottom: 25px;
636 | border-radius: 8px;
637 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
638 | }
639 |
640 | .main--title {
641 | text-align: center;
642 | }
643 |
644 | .table-container .table {
645 | width: 100%;
646 | border-collapse: collapse;
647 | }
648 |
649 | .table-container .table th,
650 | .table-container .table td {
651 | border: 1px solid #ddd;
652 | padding: 8px;
653 | }
654 |
655 | .table-container .table th {
656 | text-align: left;
657 | color: white;
658 | font-size: 13px;
659 | background-color: #c2c0a6;
660 | }
661 |
662 | .table-container .table td a {
663 | background-color: black;
664 | text-decoration: none;
665 | }
666 |
667 | table {
668 | width: 100%;
669 | border-collapse: collapse;
670 | background-color: #fff;
671 | border-radius: 2px;
672 | overflow: hidden;
673 | transition: background-color 0.3s, color 0.3s;
674 | }
675 |
676 | thead {
677 | background-color: #f0f0f0;
678 | }
679 |
680 | thead tr th {
681 | padding: 1rem;
682 | text-align: left;
683 | font-weight: 600;
684 | color: #666;
685 | }
686 |
687 | tbody tr td {
688 | padding: 1rem;
689 | border-bottom: 1px solid #e0e0e0;
690 | font-size: 1.03rem;
691 | color: #333;
692 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
693 | font-weight: 490;
694 | letter-spacing: 0.5px;
695 | }
696 |
697 | tbody tr:last-child td {
698 | border-bottom: none;
699 | }
700 |
701 | /* Line, Pie, Bar charts Section */
702 | .three-line-charts-wrapper {
703 | display: flex;
704 | justify-content: space-around;
705 | align-items: flex-start;
706 | gap: 2rem;
707 | margin: 20px 0;
708 | overflow-x: auto;
709 | }
710 |
711 | .single-line-box canvas {
712 | max-width: 300px;
713 | max-height: 250px;
714 | }
715 |
716 | .single-line-box {
717 | background-color: #fff;
718 | border-radius: 5px;
719 | padding: 1rem;
720 | flex: 1;
721 | text-align: center;
722 | color: #333;
723 | min-width: 200px;
724 | }
725 |
726 | .single-pie-box canvas {
727 | max-width: 250px;
728 | max-height: 250px;
729 | }
730 |
731 | .three-pie-charts-wrapper {
732 | display: flex;
733 | justify-content: space-around;
734 | align-items: flex-start;
735 | gap: 2rem;
736 | margin: 20px 0;
737 | overflow-x: auto;
738 | }
739 |
740 | .single-pie-box {
741 | background-color: #fff;
742 | border-radius: 5px;
743 | padding: 1rem;
744 | flex: 1;
745 | text-align: center;
746 | color: #333;
747 | min-width: 200px;
748 | }
749 |
750 | h1 {
751 | margin-bottom: 1rem;
752 | text-align: center;
753 | }
754 |
755 | .single-bar-box canvas {
756 | max-width: 250px;
757 | max-height: 250px;
758 | }
759 |
760 | .three-bar-charts-wrapper {
761 | display: flex;
762 | justify-content: space-around;
763 | align-items: flex-start;
764 | gap: 2rem;
765 | margin: 20px 0;
766 | overflow-x: auto;
767 | }
768 |
769 | .single-bar-box {
770 | background-color: #fff;
771 | border-radius: 5px;
772 | padding: 1rem;
773 | flex: 1;
774 | text-align: center;
775 | color: #333;
776 | min-width: 200px;
777 | }
778 | /* End Line, Pie, Bar charts Section */
779 |
780 | /* Container for profile and content sections */
781 | .profile-container {
782 | display: flex;
783 | gap: 2rem;
784 | }
785 |
786 | .profile-user {
787 | text-align: center;
788 | margin-bottom: 1rem;
789 | }
790 |
791 | .profile-menu li a {
792 | text-decoration: none;
793 | color: #333;
794 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
795 | font-weight: 640;
796 | }
797 |
798 | .profile-left-panel {
799 | flex: 0 0 250px;
800 | background-color: #fff;
801 | padding: 1rem;
802 | border-radius: 5px;
803 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
804 | }
805 |
806 | /* Right panel (content sections) styling */
807 | .content-section {
808 | width: 100%;
809 | display: none;
810 | background-color: #fff;
811 | padding: 1rem;
812 | border-radius: 5px;
813 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
814 | }
815 |
816 | .content-section {
817 | display: none;
818 | }
819 | .active-section {
820 | display: block;
821 | }
822 | .profile-menu li {
823 | list-style: none;
824 | padding: 10px 0;
825 | }
826 | .profile-menu li a {
827 | text-decoration: none;
828 | cursor: pointer;
829 | }
830 | .profile-menu li a.active-link {
831 | font-weight: bold;
832 | color: blue;
833 | }
834 |
835 | .form-group {
836 | margin-bottom: 1rem;
837 | }
838 | .form-group label {
839 | display: block;
840 | margin-bottom: 0.25rem;
841 | }
842 | .form-group input {
843 | width: 100%;
844 | border: 1px solid #ccc;
845 | }
846 | /* End Container for profile and content sections */
847 |
848 | /* Dashboard theme styling */
849 | .dark-theme {
850 | background-color: #242435;
851 | color: #fff;
852 | }
853 |
854 | .dark-theme .topbar {
855 | background-color: #242435;
856 | border-color: #444;
857 | }
858 |
859 | .dark-theme .search-container input {
860 | /*background-color: #333;*/
861 | color: #fff;
862 | border-color: #444;
863 | }
864 |
865 | .dark-theme .sidebar {
866 | background-color: #242435;
867 | border-color: #444;
868 | }
869 |
870 | .dark-theme .logo,
871 | .dark-theme .kitchen-name,
872 | .dark-theme .nav-menu li a,
873 | .dark-theme .nav-menu li a i {
874 | color: #fff;
875 | }
876 |
877 | .dark-theme .nav-menu li a:hover {
878 | background-color: #333;
879 | }
880 |
881 | .dark-theme .dropdown-menu {
882 | background-color: #242435;
883 | border-color: #444;
884 | }
885 |
886 | .dark-theme .dropdown-menu ul li a {
887 | color: #fff;
888 | }
889 |
890 | .dark-theme .main-content,
891 | .dark-theme .table-container,
892 | .dark-theme table {
893 | background-color: #242435;
894 | color: #fff;
895 | }
896 |
897 | .dark-theme .table-container {
898 | background-color: #242435;
899 | color: #fff;
900 | box-shadow: 0 0 10px rgb(133, 133, 133);
901 | }
902 |
903 | .dark-theme thead {
904 | background-color: #333;
905 | }
906 |
907 | .dark-theme .invoice-footer,
908 | .dark-theme .invoice-total {
909 | background-color: #242435;
910 | border-color: #444;
911 | }
912 |
913 | /* Additional Dark Theme Overrides */
914 | .dark-theme .user-profile span {
915 | color: #fff !important;
916 | }
917 |
918 | .dark-theme .content-header h1,
919 | .dark-theme .content-header .invoice-meta,
920 | .dark-theme .invoice-info p {
921 | color: #fff !important;
922 | }
923 |
924 | .dark-theme table thead tr th,
925 | .dark-theme table tbody tr td {
926 | color: #fff !important;
927 | }
928 |
929 | .dark-theme .nav-menu li a {
930 | color: #fff !important;
931 | }
932 |
933 | /* If you want the icons to also turn white in dark theme: */
934 | .dark-theme .nav-menu li a i,
935 | .dark-theme .user-profile i {
936 | color: #fff !important;
937 | }
938 |
939 | .dark-theme .table,
940 | .dark-theme .table tbody td {
941 | background-color: #242435 !important;
942 | color: #fff !important;
943 | }
944 |
945 | .dark-theme .table thead tr {
946 | background-color: #f0f0f0 !important;
947 | }
948 |
949 | .dark-theme .table thead th {
950 | color: #333 !important;
951 | }
952 |
953 | .dark-theme .profile-left-panel {
954 | background-color: #242435;
955 | color: #fff;
956 | box-shadow: 0 0 10px rgb(133, 133, 133);
957 | }
958 |
959 | .dark-theme .content-section {
960 | background-color: #242435;
961 | color: #fff;
962 | box-shadow: 0 0 10px rgb(133, 133, 133);
963 | }
964 |
965 | .dark-theme .profile-left-panel ul li a,
966 | .dark-theme .content-section h3,
967 | .dark-theme .profile-details p {
968 | color: #fff;
969 | }
970 |
971 | /* Scroll to top button icon styles */
972 | #scrollTopBtn {
973 | display: none; /* Hidden by default */
974 | position: fixed;
975 | bottom: 30px;
976 | right: 20px;
977 | z-index: 99; /* Stay on top of other elements */
978 | font-size: 18px;
979 | background-color: #31db64;
980 | color: white;
981 | font-weight: 840;
982 | border: none;
983 | outline: none;
984 | width: 40px; /* Reduce the width */
985 | height: 40px; /* Reduce the height */
986 | cursor: pointer;
987 | padding: 2px;
988 | border-radius: 100px;
989 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
990 | }
991 |
992 | #scrollTopBtn:hover {
993 | background-color: #31db64;
994 | }
995 | /* End scroll to top button icon styles */
996 |
997 | /* Media query to hide the button on mobile */
998 | @media (max-width: 375px) {
999 | .input-group button {
1000 | display: none;
1001 | }
1002 | }
1003 | /* End top bar search input section */
1004 |
1005 | @media screen and (max-width: 480px) {
1006 | .sidebar {
1007 | display: none;
1008 | }
1009 | .main-section {
1010 | flex-direction: column;
1011 | margin-left: 0;
1012 | }
1013 | .topbar {
1014 | padding: 0 1rem;
1015 | }
1016 | }
1017 |
1018 | @media only screen and (min-width: 768px) and (max-width: 810px) {
1019 | .sidebar {
1020 | width: 200px;
1021 | }
1022 | .main-section {
1023 | margin-left: 200px;
1024 | }
1025 | }
1026 |
1027 | @media only screen and (min-width: 320px) {
1028 | .three-line-charts-wrapper {
1029 | flex-direction: column;
1030 | align-items: center;
1031 | }
1032 | .single-line-box {
1033 | max-width: 100% !important;
1034 | }
1035 | }
1036 |
1037 | @media only screen and (min-width: 768px) and (max-width: 810px) {
1038 | .three-line-charts-wrapper {
1039 | flex-direction: row;
1040 | align-items: center;
1041 | }
1042 | .single-line-box {
1043 | max-width: 100% !important;
1044 | }
1045 | }
1046 |
1047 | @media only screen and (min-width: 1024px) {
1048 | .three-line-charts-wrapper {
1049 | flex-direction: row;
1050 | align-items: center;
1051 | }
1052 | }
1053 |
1054 | @media only screen and (min-width: 320px) {
1055 | .three-pie-charts-wrapper {
1056 | flex-direction: column;
1057 | align-items: center;
1058 | }
1059 | .single-pie-box {
1060 | max-width: 100% !important;
1061 | }
1062 | }
1063 |
1064 | @media only screen and (min-width: 768px) and (max-width: 810px) {
1065 | .three-pie-charts-wrapper {
1066 | flex-direction: row;
1067 | align-items: center;
1068 | }
1069 | .single-pie-box {
1070 | max-width: 100% !important;
1071 | }
1072 | }
1073 |
1074 | @media only screen and (min-width: 1024px) {
1075 | .three-pie-charts-wrapper {
1076 | flex-direction: row;
1077 | align-items: center;
1078 | }
1079 | }
1080 |
1081 | @media only screen and (min-width: 320px) {
1082 | .three-bar-charts-wrapper {
1083 | flex-direction: column;
1084 | align-items: center;
1085 | }
1086 | .single-bar-box {
1087 | max-width: 100% !important;
1088 | }
1089 | }
1090 |
1091 | @media only screen and (min-width: 768px) and (max-width: 810px) {
1092 | .three-bar-charts-wrapper {
1093 | flex-direction: row;
1094 | align-items: center;
1095 | }
1096 | .single-bar-box {
1097 | max-width: 100% !important;
1098 | }
1099 | }
1100 |
1101 | @media only screen and (min-width: 1024px) {
1102 | .three-bar-charts-wrapper {
1103 | flex-direction: row;
1104 | align-items: center;
1105 | }
1106 | }
1107 |
1108 | @media only screen and (min-width: 810px) {
1109 | .profile-left-panel {
1110 | width: 100%;
1111 | }
1112 |
1113 | .content-section {
1114 | width: 100%;
1115 | }
1116 | }
1117 |
1118 | @media (max-width: 375px) {
1119 | .profile-container {
1120 | flex-direction: column;
1121 | }
1122 |
1123 | .profile-left-panel,
1124 | .content-section {
1125 | width: 100%;
1126 | }
1127 | }
1128 |
1129 | /* Responsive media queries (Mobile View)*/
1130 | @media screen and (max-width: 480px) {
1131 | .sidebar {
1132 | display: block;
1133 | transform: translateX(-100%);
1134 | /* transition: transform 0.3s ease; */
1135 | transition: opacity 0.8s ease, transform 0.8s ease;
1136 | width: 85%;
1137 | }
1138 |
1139 | .sidebar.closed {
1140 | transform: translateX(0);
1141 | }
1142 |
1143 | .main-section.sidebar-closed {
1144 | margin-left: 0;
1145 | }
1146 |
1147 | .toggle-sidebar {
1148 | display: block;
1149 | }
1150 | }
1151 | /* End responsive media queries (Mobile View)*/
1152 |
1153 | @media (max-width: 600px) {
1154 | .dashboard-box p {
1155 | font-size: 1.2rem;
1156 | }
1157 | .dashboard-box h2 {
1158 | font-size: 1rem;
1159 | }
1160 | }
1161 |
1162 | i {
1163 | transform: rotate(0deg); /* Ensure no rotation */
1164 | -webkit-transform: rotate(0deg);
1165 | -moz-transform: rotate(0deg);
1166 | }
1167 |
1168 | .user-profile i {
1169 | transform: none !important; /* Override any global transform */
1170 | -webkit-transform: none !important;
1171 | }
1172 | """
1173 |
1174 | FLASH_MESSAGE =\
1175 | """
1176 |
1177 | /* CSS Animation for sliding-in the flash message */
1178 | @keyframes slideIn {
1179 | from {
1180 | transform: translateX(100%);
1181 | }
1182 | to {
1183 | transform: translateX(0);
1184 | }
1185 | }
1186 |
1187 | /* CSS Animation for fading-out the flash message */
1188 | @keyframes fadeOut {
1189 | from {
1190 | opacity: 1;
1191 | }
1192 | to {
1193 | opacity: 0;
1194 | }
1195 | }
1196 |
1197 | /* Styling for the flash message container */
1198 | .flash--message {
1199 | position: fixed;
1200 | top: 18px;
1201 | right: 20px;
1202 | z-index: 9999;
1203 | height: auto; /* Set height to auto */
1204 | line-height: 28px; /* Adjust line-height */
1205 | }
1206 |
1207 | /* Styling for each flash message */
1208 | .alert {
1209 | animation: slideIn 0.3s forwards, fadeOut 0.3s 4s forwards;
1210 | margin-bottom: 5px; /* Add space between messages if there are multiple */
1211 | padding: 5px 10px; /* Adjust padding to reduce height */
1212 | }
1213 |
1214 | .fas.fa-circle-check {
1215 | color: green;
1216 | }
1217 |
1218 | .fas.fa-circle-xmark {
1219 | color: #e3371e;
1220 | }
1221 | """
1222 |
1223 |
1224 | BASE_FOOTER = \
1225 | """
1226 | .footer {
1227 | background: hsl(240, 14%, 14%);
1228 | color: #d3d3d3;
1229 | position: relative;
1230 | }
1231 |
1232 | .footer .footer-bottom {
1233 | left: 0;
1234 | bottom: 0;
1235 | margin: 0;
1236 | width: 100%;
1237 | height: 25px;
1238 | font-size: 15px;
1239 | position: fixed;
1240 | color: #e7900c;
1241 | padding-top: 2px;
1242 | font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
1243 | text-align: center;
1244 | }
1245 |
1246 | /* Media query for mobile */
1247 | @media only screen and (max-width: 767px) {
1248 | .footer {
1249 | height: auto;
1250 | }
1251 | }
1252 |
1253 | /* Tablets and Small Screens */
1254 | @media only screen and (min-width: 768px) and (max-width: 991px) {
1255 | .footer {
1256 | height: auto;
1257 | }
1258 | }
1259 | """
1260 |
1261 | BAD_REQUESTS =\
1262 | """
1263 | * {
1264 | margin: 0;
1265 | padding: 0;
1266 | box-sizing: border-box;
1267 | font-family: "Courier New", Courier, monospace;
1268 | }
1269 |
1270 | body,
1271 | html {
1272 | height: 100%;
1273 | overflow: hidden;
1274 | background-color: #1b1b1b;
1275 | color: white;
1276 | }
1277 |
1278 | .container {
1279 | display: flex;
1280 | align-items: center;
1281 | justify-content: center;
1282 | height: 100%;
1283 | position: relative;
1284 | }
1285 |
1286 | .glitch {
1287 | position: relative;
1288 | text-align: center;
1289 | color: white;
1290 | }
1291 |
1292 | h1 {
1293 | font-size: 8rem;
1294 | color: #ff0033;
1295 | animation: glitch-animation 2s infinite;
1296 | position: relative;
1297 | }
1298 |
1299 | h2 {
1300 | font-size: 2.5rem;
1301 | margin-bottom: 1rem;
1302 | color: #ff0033;
1303 | animation: glitch-animation 3s infinite;
1304 | }
1305 |
1306 | p {
1307 | font-size: 1.5rem;
1308 | margin-bottom: 2rem;
1309 | }
1310 |
1311 | button {
1312 | padding: 0.75rem 2rem;
1313 | background-color: #ff0033;
1314 | border: none;
1315 | border-radius: 5px;
1316 | color: white;
1317 | font-size: 1.25rem;
1318 | cursor: pointer;
1319 | transition: background-color 0.3s ease;
1320 | }
1321 |
1322 | button:hover {
1323 | background-color: #e60029;
1324 | }
1325 |
1326 | @keyframes glitch-animation {
1327 | 0% {
1328 | transform: translate(0);
1329 | }
1330 | 10% {
1331 | transform: translate(-5px, 5px);
1332 | }
1333 | 20% {
1334 | transform: translate(5px, -5px);
1335 | }
1336 | 30% {
1337 | transform: translate(-5px, -5px);
1338 | }
1339 | 40% {
1340 | transform: translate(5px, 5px);
1341 | }
1342 | 50% {
1343 | transform: translate(-5px, 5px);
1344 | }
1345 | 60% {
1346 | transform: translate(5px, -5px);
1347 | }
1348 | 70% {
1349 | transform: translate(-5px, -5px);
1350 | }
1351 | 80% {
1352 | transform: translate(5px, 5px);
1353 | }
1354 | 90% {
1355 | transform: translate(-5px, 5px);
1356 | }
1357 | 100% {
1358 | transform: translate(0);
1359 | }
1360 | }
1361 |
1362 | /* Media Queries for responsiveness */
1363 | @media screen and (max-width: 768px) {
1364 | h1 {
1365 | font-size: 5rem;
1366 | }
1367 |
1368 | h2 {
1369 | font-size: 2rem;
1370 | }
1371 |
1372 | p {
1373 | font-size: 1.2rem;
1374 | }
1375 |
1376 | button {
1377 | font-size: 1rem;
1378 | }
1379 | }
1380 |
1381 | @media screen and (max-width: 480px) {
1382 | h1 {
1383 | font-size: 3rem;
1384 | }
1385 |
1386 | h2 {
1387 | font-size: 1.5rem;
1388 | }
1389 |
1390 | p {
1391 | font-size: 1rem;
1392 | }
1393 |
1394 | button {
1395 | font-size: 0.9rem;
1396 | }
1397 | }
1398 | """
1399 |
1400 |
1401 | CONTENT_TOO_LARGE =\
1402 | """
1403 | * {
1404 | margin: 0;
1405 | padding: 0;
1406 | box-sizing: border-box;
1407 | font-family: 'Open Sans', sans-serif;
1408 | }
1409 |
1410 | body, html {
1411 | height: 100%;
1412 | display: flex;
1413 | align-items: center;
1414 | justify-content: center;
1415 | background-color: #f1c40f;
1416 | color: #333;
1417 | }
1418 |
1419 | .container {
1420 | text-align: center;
1421 | padding: 20px;
1422 | }
1423 |
1424 | h1 {
1425 | font-size: 7rem;
1426 | color: #e74c3c;
1427 | margin-bottom: 1rem;
1428 | }
1429 |
1430 | h2 {
1431 | font-size: 2.5rem;
1432 | margin-bottom: 1rem;
1433 | }
1434 |
1435 | p {
1436 | font-size: 1.25rem;
1437 | margin-bottom: 2rem;
1438 | color: #2c3e50;
1439 | }
1440 |
1441 | button {
1442 | padding: 0.75rem 2rem;
1443 | background-color: #e74c3c;
1444 | border: none;
1445 | border-radius: 5px;
1446 | color: white;
1447 | font-size: 1.25rem;
1448 | cursor: pointer;
1449 | transition: background-color 0.3s ease;
1450 | }
1451 |
1452 | button:hover {
1453 | background-color: #c0392b;
1454 | }
1455 |
1456 | /* File Icon */
1457 | .file-icon {
1458 | position: relative;
1459 | width: 150px;
1460 | height: 180px;
1461 | margin: 0 auto 20px;
1462 | }
1463 |
1464 | .file {
1465 | width: 100%;
1466 | height: 100%;
1467 | background-color: #fff;
1468 | border: 5px solid #e74c3c;
1469 | border-radius: 10px;
1470 | position: relative;
1471 | animation: shake 2s infinite ease-in-out;
1472 | }
1473 |
1474 | .fold {
1475 | width: 50px;
1476 | height: 50px;
1477 | background-color: #f1c40f;
1478 | position: absolute;
1479 | top: -5px;
1480 | right: -5px;
1481 | border-top-left-radius: 10px;
1482 | transform: rotate(45deg);
1483 | }
1484 |
1485 | .exceed-text {
1486 | position: absolute;
1487 | bottom: -30px;
1488 | width: 100%;
1489 | color: #e74c3c;
1490 | font-size: 1.5rem;
1491 | font-weight: bold;
1492 | text-align: center;
1493 | animation: fade-in 2s infinite alternate;
1494 | }
1495 |
1496 | /* Animation */
1497 | @keyframes shake {
1498 | 0%, 100% {
1499 | transform: translate(0, 0);
1500 | }
1501 | 25% {
1502 | transform: translate(-5px, 5px);
1503 | }
1504 | 50% {
1505 | transform: translate(5px, -5px);
1506 | }
1507 | 75% {
1508 | transform: translate(-5px, -5px);
1509 | }
1510 | }
1511 |
1512 | @keyframes fade-in {
1513 | from {
1514 | opacity: 0;
1515 | }
1516 | to {
1517 | opacity: 1;
1518 | }
1519 | }
1520 |
1521 | /* Responsive Design */
1522 | @media screen and (max-width: 768px) {
1523 | h1 {
1524 | font-size: 5rem;
1525 | }
1526 |
1527 | h2 {
1528 | font-size: 2rem;
1529 | }
1530 |
1531 | p {
1532 | font-size: 1rem;
1533 | }
1534 |
1535 | button {
1536 | font-size: 1rem;
1537 | }
1538 |
1539 | .file-icon {
1540 | width: 120px;
1541 | height: 150px;
1542 | }
1543 |
1544 | .exceed-text {
1545 | font-size: 1.25rem;
1546 | }
1547 | }
1548 |
1549 | @media screen and (max-width: 480px) {
1550 | h1 {
1551 | font-size: 4rem;
1552 | }
1553 |
1554 | h2 {
1555 | font-size: 1.5rem;
1556 | }
1557 |
1558 | p {
1559 | font-size: 0.9rem;
1560 | }
1561 |
1562 | button {
1563 | font-size: 0.9rem;
1564 | }
1565 |
1566 | .file-icon {
1567 | width: 100px;
1568 | height: 120px;
1569 | }
1570 |
1571 | .exceed-text {
1572 | font-size: 1rem;
1573 | }
1574 | }
1575 | """
1576 |
1577 |
1578 | FORBIDDEN =\
1579 | """
1580 | * {
1581 | margin: 0;
1582 | padding: 0;
1583 | box-sizing: border-box;
1584 | font-family: 'Arial', sans-serif;
1585 | }
1586 |
1587 | body, html {
1588 | height: 100%;
1589 | display: flex;
1590 | align-items: center;
1591 | justify-content: center;
1592 | background-color: #34495e;
1593 | color: #ecf0f1;
1594 | }
1595 |
1596 | .container {
1597 | text-align: center;
1598 | padding: 20px;
1599 | }
1600 |
1601 | h2 {
1602 | font-size: 2.5rem;
1603 | margin-bottom: 1rem;
1604 | }
1605 |
1606 | p {
1607 | font-size: 1.25rem;
1608 | margin-bottom: 2rem;
1609 | color: #bdc3c7;
1610 | }
1611 |
1612 | button {
1613 | padding: 0.75rem 2rem;
1614 | background-color: #e74c3c;
1615 | border: none;
1616 | border-radius: 5px;
1617 | color: white;
1618 | font-size: 1.25rem;
1619 | cursor: pointer;
1620 | transition: background-color 0.3s ease;
1621 | }
1622 |
1623 | button:hover {
1624 | background-color: #c0392b;
1625 | }
1626 |
1627 | /* Stop Sign Animation */
1628 | .stop-icon {
1629 | position: relative;
1630 | width: 100px;
1631 | height: 100px;
1632 | margin: 0 auto 20px;
1633 | }
1634 |
1635 | .stop-sign {
1636 | width: 100px;
1637 | height: 100px;
1638 | background-color: #e74c3c;
1639 | border-radius: 10px;
1640 | position: relative;
1641 | display: flex;
1642 | align-items: center;
1643 | justify-content: center;
1644 | color: white;
1645 | font-size: 2rem;
1646 | font-weight: bold;
1647 | animation: bounce 2s infinite ease-in-out;
1648 | }
1649 |
1650 | .stop-sign::before {
1651 | content: "";
1652 | position: absolute;
1653 | width: 10px;
1654 | height: 100px;
1655 | background-color: #e74c3c;
1656 | top: 50%;
1657 | left: 50%;
1658 | transform: rotate(45deg) translate(-50%, -50%);
1659 | border-radius: 5px;
1660 | }
1661 |
1662 | .stop-sign::after {
1663 | content: "";
1664 | position: absolute;
1665 | width: 100px;
1666 | height: 10px;
1667 | background-color: #e74c3c;
1668 | top: 50%;
1669 | left: 50%;
1670 | transform: rotate(45deg) translate(-50%, -50%);
1671 | border-radius: 5px;
1672 | }
1673 |
1674 | @keyframes bounce {
1675 | 0%, 100% {
1676 | transform: translateY(0);
1677 | }
1678 | 50% {
1679 | transform: translateY(-10px);
1680 | }
1681 | }
1682 |
1683 | /* Responsive Design */
1684 | @media screen and (max-width: 768px) {
1685 | h2 {
1686 | font-size: 2rem;
1687 | }
1688 |
1689 | p {
1690 | font-size: 1rem;
1691 | }
1692 |
1693 | button {
1694 | font-size: 1rem;
1695 | }
1696 |
1697 | .stop-sign {
1698 | width: 80px;
1699 | height: 80px;
1700 | font-size: 1.5rem;
1701 | }
1702 | }
1703 |
1704 | @media screen and (max-width: 480px) {
1705 | h2 {
1706 | font-size: 1.5rem;
1707 | }
1708 |
1709 | p {
1710 | font-size: 0.9rem;
1711 | }
1712 |
1713 | button {
1714 | font-size: 0.9rem;
1715 | }
1716 |
1717 | .stop-sign {
1718 | width: 60px;
1719 | height: 60px;
1720 | font-size: 1.25rem;
1721 | }
1722 | }
1723 | """
1724 |
1725 |
1726 | INTERNAL_SERVER =\
1727 | """
1728 | * {
1729 | margin: 0;
1730 | padding: 0;
1731 | box-sizing: border-box;
1732 | font-family: "Montserrat", sans-serif;
1733 | }
1734 |
1735 | body,
1736 | html {
1737 | height: 100%;
1738 | overflow: hidden;
1739 | background-color: #2c3e50;
1740 | color: white;
1741 | display: flex;
1742 | align-items: center;
1743 | justify-content: center;
1744 | }
1745 |
1746 | .container {
1747 | position: relative;
1748 | text-align: center;
1749 | z-index: 1;
1750 | }
1751 |
1752 | h1 {
1753 | font-size: 10rem;
1754 | margin-bottom: 1rem;
1755 | color: #e74c3c;
1756 | animation: glitch-animation 1.5s infinite;
1757 | }
1758 |
1759 | h2 {
1760 | font-size: 2.5rem;
1761 | margin-bottom: 1rem;
1762 | color: white;
1763 | animation: glitch-animation 2s infinite;
1764 | }
1765 |
1766 | p {
1767 | font-size: 1rem;
1768 | margin-bottom: 2rem;
1769 | color: #ecf0f1;
1770 | }
1771 |
1772 | button {
1773 | padding: 0.75rem 2rem;
1774 | background-color: #e74c3c;
1775 | border: none;
1776 | margin-bottom: 1rem;
1777 | border-radius: 5px;
1778 | color: white;
1779 | font-size: 1.2rem;
1780 | cursor: pointer;
1781 | transition: background-color 0.3s ease;
1782 | }
1783 |
1784 | button:hover {
1785 | background-color: #c0392b;
1786 | }
1787 |
1788 | /* Glitch Effect */
1789 | @keyframes glitch-animation {
1790 | 0% {
1791 | text-shadow: none;
1792 | }
1793 | 20% {
1794 | text-shadow: -2px 0 red, 2px 0 blue;
1795 | }
1796 | 40% {
1797 | text-shadow: 2px 0 red, -2px 0 blue;
1798 | }
1799 | 60% {
1800 | text-shadow: -2px 0 red, 2px 0 blue;
1801 | }
1802 | 80% {
1803 | text-shadow: 2px 0 red, -2px 0 blue;
1804 | }
1805 | 100% {
1806 | text-shadow: none;
1807 | }
1808 | }
1809 |
1810 | .glitch-background {
1811 | position: absolute;
1812 | top: 0;
1813 | left: 0;
1814 | width: 100%;
1815 | height: 100%;
1816 | background: linear-gradient(
1817 | 45deg,
1818 | rgba(231, 76, 60, 0.3),
1819 | rgba(41, 128, 185, 0.3)
1820 | );
1821 | z-index: -1;
1822 | animation: flicker 3s infinite;
1823 | }
1824 |
1825 | @keyframes flicker {
1826 | 0% {
1827 | opacity: 0.5;
1828 | }
1829 | 50% {
1830 | opacity: 0.8;
1831 | }
1832 | 100% {
1833 | opacity: 0.5;
1834 | }
1835 | }
1836 |
1837 | /* Media Queries for responsiveness */
1838 | @media screen and (max-width: 768px) {
1839 | h1 {
1840 | font-size: 6rem;
1841 | }
1842 |
1843 | h2 {
1844 | font-size: 2rem;
1845 | }
1846 |
1847 | p {
1848 | font-size: 1rem;
1849 | }
1850 |
1851 | button {
1852 | font-size: 1rem;
1853 | }
1854 | }
1855 |
1856 | @media screen and (max-width: 480px) {
1857 | h1 {
1858 | font-size: 4rem;
1859 | }
1860 |
1861 | h2 {
1862 | font-size: 1.5rem;
1863 | }
1864 |
1865 | p {
1866 | font-size: 0.9rem;
1867 | }
1868 |
1869 | button {
1870 | font-size: 0.9rem;
1871 | }
1872 | }
1873 | """
1874 |
1875 |
1876 | MAINTAINANCE =\
1877 | """
1878 |
1879 | * {
1880 | margin: 0;
1881 | padding: 0;
1882 | box-sizing: border-box;
1883 | font-family: 'Montserrat', sans-serif;
1884 | }
1885 |
1886 | body, html {
1887 | height: 100%;
1888 | background-color: #34495e;
1889 | display: flex;
1890 | align-items: center;
1891 | justify-content: center;
1892 | color: #ecf0f1;
1893 | }
1894 |
1895 | .container {
1896 | text-align: center;
1897 | padding: 20px;
1898 | }
1899 |
1900 | h1 {
1901 | font-size: 5rem;
1902 | color: #e74c3c;
1903 | margin-bottom: 1rem;
1904 | }
1905 |
1906 | h2 {
1907 | font-size: 2.5rem;
1908 | margin-bottom: 1rem;
1909 | }
1910 |
1911 | p {
1912 | font-size: 1.25rem;
1913 | margin-bottom: 2rem;
1914 | color: #bdc3c7;
1915 | }
1916 |
1917 | button {
1918 | padding: 0.75rem 2rem;
1919 | background-color: #e74c3c;
1920 | border: none;
1921 | border-radius: 5px;
1922 | color: white;
1923 | font-size: 1.25rem;
1924 | cursor: pointer;
1925 | transition: background-color 0.3s ease;
1926 | }
1927 |
1928 | button:hover {
1929 | background-color: #c0392b;
1930 | }
1931 |
1932 | /* Gear Icon */
1933 | .gear-icon {
1934 | width: 120px;
1935 | height: 120px;
1936 | margin: 0 auto 20px;
1937 | position: relative;
1938 | }
1939 |
1940 | .gear {
1941 | width: 100%;
1942 | height: 100%;
1943 | background-color: #ecf0f1;
1944 | border-radius: 50%;
1945 | position: relative;
1946 | box-shadow: 0 0 15px rgba(0, 0, 0, 0.3);
1947 | animation: rotate 3s infinite linear;
1948 | }
1949 |
1950 | .gear:before, .gear:after {
1951 | content: '';
1952 | position: absolute;
1953 | background-color: #e74c3c;
1954 | border-radius: 5px;
1955 | }
1956 |
1957 | .gear:before {
1958 | width: 60%;
1959 | height: 10%;
1960 | top: 45%;
1961 | left: 20%;
1962 | }
1963 |
1964 | .gear:after {
1965 | width: 10%;
1966 | height: 60%;
1967 | top: 20%;
1968 | left: 45%;
1969 | }
1970 |
1971 | /* Rotation Animation */
1972 | @keyframes rotate {
1973 | 0% {
1974 | transform: rotate(0deg);
1975 | }
1976 | 100% {
1977 | transform: rotate(360deg);
1978 | }
1979 | }
1980 |
1981 | /* Responsive Design */
1982 | @media screen and (max-width: 768px) {
1983 | h1 {
1984 | font-size: 5rem;
1985 | }
1986 |
1987 | h2 {
1988 | font-size: 2rem;
1989 | }
1990 |
1991 | p {
1992 | font-size: 1rem;
1993 | }
1994 |
1995 | button {
1996 | font-size: 1rem;
1997 | }
1998 |
1999 | .gear-icon {
2000 | width: 100px;
2001 | height: 100px;
2002 | }
2003 | }
2004 |
2005 | @media screen and (max-width: 480px) {
2006 | h1 {
2007 | font-size: 4rem;
2008 | }
2009 |
2010 | h2 {
2011 | font-size: 1.5rem;
2012 | }
2013 |
2014 | p {
2015 | font-size: 0.9rem;
2016 | }
2017 |
2018 | button {
2019 | font-size: 0.9rem;
2020 | }
2021 |
2022 | .gear-icon {
2023 | width: 80px;
2024 | height: 80px;
2025 | }
2026 | }
2027 | """
2028 |
2029 |
2030 | NOT_FOUND =\
2031 | """
2032 | * {
2033 | margin: 0;
2034 | padding: 0;
2035 | box-sizing: border-box;
2036 | font-family: "Arial", sans-serif;
2037 | }
2038 |
2039 | body,
2040 | html {
2041 | height: 100%;
2042 | overflow: hidden;
2043 | }
2044 |
2045 | .container {
2046 | position: relative;
2047 | display: flex;
2048 | align-items: center;
2049 | justify-content: center;
2050 | height: 100%;
2051 | background: #000;
2052 | color: white;
2053 | text-align: center;
2054 | }
2055 |
2056 | .error {
2057 | z-index: 10;
2058 | }
2059 |
2060 | h1 {
2061 | font-size: 10rem;
2062 | margin-bottom: 1rem;
2063 | color: #f39c12;
2064 | animation: glow 1s ease-in-out infinite alternate;
2065 | }
2066 |
2067 | p {
2068 | font-size: 1.5rem;
2069 | margin-bottom: 2rem;
2070 | }
2071 |
2072 | button {
2073 | padding: 0.75rem 2rem;
2074 | background-color: #f39c12;
2075 | border: none;
2076 | border-radius: 5px;
2077 | color: white;
2078 | font-size: 1.25rem;
2079 | cursor: pointer;
2080 | transition: background-color 0.3s ease;
2081 | }
2082 |
2083 | button:hover {
2084 | background-color: #e67e22;
2085 | }
2086 |
2087 | .stars {
2088 | position: absolute;
2089 | width: 100%;
2090 | height: 100%;
2091 | background: transparent;
2092 | z-index: 0;
2093 | overflow: hidden;
2094 | pointer-events: none;
2095 | }
2096 |
2097 | .stars::after {
2098 | content: "";
2099 | position: absolute;
2100 | top: 0;
2101 | left: 0;
2102 | width: 100%;
2103 | height: 100%;
2104 | background: radial-gradient(circle at 50% 50%, #f39c12 0%, transparent 80%);
2105 | animation: rotate 120s linear infinite;
2106 | opacity: 0.3;
2107 | }
2108 |
2109 | @keyframes glow {
2110 | from {
2111 | text-shadow: 0 0 10px #f39c12, 0 0 20px #f39c12, 0 0 30px #e67e22;
2112 | }
2113 | to {
2114 | text-shadow: 0 0 20px #e67e22, 0 0 40px #f39c12, 0 0 50px #e67e22;
2115 | }
2116 | }
2117 |
2118 | @keyframes rotate {
2119 | 0% {
2120 | transform: rotate(0deg);
2121 | }
2122 | 100% {
2123 | transform: rotate(360deg);
2124 | }
2125 | }
2126 |
2127 | /* Media Queries for responsiveness */
2128 | @media screen and (max-width: 768px) {
2129 | h1 {
2130 | font-size: 6rem;
2131 | }
2132 |
2133 | p {
2134 | font-size: 1.2rem;
2135 | }
2136 |
2137 | button {
2138 | font-size: 1rem;
2139 | }
2140 | }
2141 |
2142 | @media screen and (max-width: 480px) {
2143 | h1 {
2144 | font-size: 4rem;
2145 | }
2146 |
2147 | p {
2148 | font-size: 1rem;
2149 | }
2150 |
2151 | button {
2152 | font-size: 0.9rem;
2153 | }
2154 | }
2155 | """
2156 |
2157 |
2158 | TOO_MANAY_REQUEST =\
2159 | """
2160 | * {
2161 | margin: 0;
2162 | padding: 0;
2163 | box-sizing: border-box;
2164 | font-family: "Verdana", sans-serif;
2165 | }
2166 |
2167 | body,
2168 | html {
2169 | height: 100%;
2170 | background-color: #2c3e50;
2171 | color: #ecf0f1;
2172 | display: flex;
2173 | align-items: center;
2174 | justify-content: center;
2175 | font-size: 13px;
2176 | }
2177 |
2178 | .container {
2179 | text-align: center;
2180 | padding: 15px;
2181 | width: 93%;
2182 | background-color: #34495e;
2183 | border-radius: 10px;
2184 | box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1);
2185 | }
2186 |
2187 | h1 {
2188 | font-size: 5rem;
2189 | color: #e74c3c;
2190 | }
2191 |
2192 | h2 {
2193 | font-size: 2rem;
2194 | margin-bottom: 1rem;
2195 | color: #ecf0f1;
2196 | }
2197 |
2198 | p {
2199 | font-size: 1rem;
2200 | margin-bottom: 2rem;
2201 | }
2202 |
2203 | .timer {
2204 | display: flex;
2205 | flex-direction: column;
2206 | align-items: center;
2207 | margin-bottom: 2rem;
2208 | }
2209 |
2210 | .timer span {
2211 | font-size: 3rem;
2212 | color: #f39c12;
2213 | animation: pulse 1s infinite;
2214 | }
2215 |
2216 | button {
2217 | padding: 0.75rem 2rem;
2218 | background-color: #e74c3c;
2219 | border: none;
2220 | border-radius: 5px;
2221 | color: white;
2222 | font-size: 1.25rem;
2223 | cursor: pointer;
2224 | transition: background-color 0.3s ease;
2225 | }
2226 |
2227 | button:hover {
2228 | background-color: #c0392b;
2229 | }
2230 |
2231 | @keyframes pulse {
2232 | 0% {
2233 | transform: scale(1);
2234 | }
2235 | 50% {
2236 | transform: scale(1.1);
2237 | }
2238 | 100% {
2239 | transform: scale(1);
2240 | }
2241 | }
2242 |
2243 | /* Media Queries for responsiveness */
2244 | @media screen and (max-width: 768px) {
2245 | h1 {
2246 | font-size: 6rem;
2247 | }
2248 |
2249 | h2 {
2250 | font-size: 2rem;
2251 | }
2252 |
2253 | p {
2254 | font-size: 1rem;
2255 | }
2256 |
2257 | button {
2258 | font-size: 1rem;
2259 | }
2260 | }
2261 |
2262 | @media screen and (max-width: 480px) {
2263 | h1 {
2264 | font-size: 4rem;
2265 | }
2266 |
2267 | h2 {
2268 | font-size: 1.5rem;
2269 | }
2270 |
2271 | p {
2272 | font-size: 0.9rem;
2273 | }
2274 |
2275 | button {
2276 | font-size: 0.9rem;
2277 | }
2278 | }
2279 | """
2280 |
2281 |
2282 | UNAUTHORIZED =\
2283 | """
2284 | * {
2285 | margin: 0;
2286 | padding: 0;
2287 | box-sizing: border-box;
2288 | font-family: "Arial", sans-serif;
2289 | }
2290 |
2291 | body,
2292 | html {
2293 | height: 100%;
2294 | display: flex;
2295 | align-items: center;
2296 | justify-content: center;
2297 | background-color: #2c3e50;
2298 | color: #ecf0f1;
2299 | }
2300 |
2301 | .container {
2302 | text-align: center;
2303 | padding: 20px;
2304 | }
2305 |
2306 | h1 {
2307 | font-size: 7rem;
2308 | color: #e74c3c;
2309 | margin-bottom: 1rem;
2310 | }
2311 |
2312 | h2 {
2313 | font-size: 2.5rem;
2314 | margin-bottom: 1rem;
2315 | }
2316 |
2317 | p {
2318 | font-size: 1.25rem;
2319 | margin-bottom: 2rem;
2320 | color: #bdc3c7;
2321 | }
2322 |
2323 | button {
2324 | padding: 0.75rem 2rem;
2325 | background-color: #e74c3c;
2326 | border: none;
2327 | border-radius: 5px;
2328 | color: white;
2329 | font-size: 1.25rem;
2330 | cursor: pointer;
2331 | transition: background-color 0.3s ease;
2332 | }
2333 |
2334 | button:hover {
2335 | background-color: #c0392b;
2336 | }
2337 |
2338 | /* Lock Icon Animation */
2339 | .lock-icon {
2340 | position: relative;
2341 | width: 80px;
2342 | height: 100px;
2343 | margin: 0 auto 20px;
2344 | }
2345 |
2346 | .lock-body {
2347 | width: 80px;
2348 | height: 50px;
2349 | background-color: #e74c3c;
2350 | border-radius: 10px;
2351 | position: absolute;
2352 | bottom: 0;
2353 | }
2354 |
2355 | .lock-shackle {
2356 | width: 60px;
2357 | height: 60px;
2358 | border: 8px solid #e74c3c;
2359 | border-radius: 50px;
2360 | position: absolute;
2361 | top: -40px;
2362 | left: 10px;
2363 | border-bottom: none;
2364 | animation: lock-animation 2s infinite alternate ease-in-out;
2365 | }
2366 |
2367 | @keyframes lock-animation {
2368 | 0% {
2369 | transform: translateY(0);
2370 | }
2371 | 100% {
2372 | transform: translateY(-5px);
2373 | }
2374 | }
2375 |
2376 | /* Responsive Design */
2377 | @media screen and (max-width: 768px) {
2378 | h1 {
2379 | font-size: 5rem;
2380 | }
2381 |
2382 | h2 {
2383 | font-size: 2rem;
2384 | }
2385 |
2386 | p {
2387 | font-size: 1rem;
2388 | }
2389 |
2390 | button {
2391 | font-size: 1rem;
2392 | }
2393 |
2394 | .lock-icon {
2395 | width: 60px;
2396 | height: 80px;
2397 | }
2398 | }
2399 |
2400 | @media screen and (max-width: 480px) {
2401 | h1 {
2402 | font-size: 4rem;
2403 | }
2404 |
2405 | h2 {
2406 | font-size: 1.5rem;
2407 | }
2408 |
2409 | p {
2410 | font-size: 0.9rem;
2411 | }
2412 |
2413 | button {
2414 | font-size: 0.9rem;
2415 | }
2416 |
2417 | .lock-icon {
2418 | width: 50px;
2419 | height: 70px;
2420 | }
2421 | }
2422 | """
2423 |
2424 |
2425 | ACCOUNT_CSS_PROFILE =\
2426 | """
2427 | .page-wrapper {
2428 | background-color: #d7e8df;
2429 | display: flex;
2430 | justify-content: center;
2431 | align-items: center;
2432 | min-height: 100vh;
2433 | padding: 20px;
2434 | }
2435 | .container {
2436 | display: flex;
2437 | flex-wrap: wrap;
2438 | justify-content: center;
2439 | gap: 20px;
2440 | max-width: 1200px;
2441 | width: 100%;
2442 | }
2443 | .sidebar {
2444 | background: white;
2445 | padding: 20px;
2446 | border-radius: 8px;
2447 | display: flex;
2448 | flex-direction: column;
2449 | width: 300px;
2450 | height: auto;
2451 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
2452 | }
2453 | .profile-img {
2454 | width: 80px;
2455 | height: 80px;
2456 | border-radius: 50%;
2457 | }
2458 | .nav-link {
2459 | font-weight: bold;
2460 | color: black;
2461 | cursor: pointer;
2462 | }
2463 | .nav-link.active {
2464 | color: blue;
2465 | }
2466 | .content-area {
2467 | background: white;
2468 | padding: 20px;
2469 | border-radius: 8px;
2470 | flex-grow: 1;
2471 | width: 700px;
2472 | min-height: 500px;
2473 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
2474 | }
2475 | .form-container,
2476 | .reset-container,
2477 | .profile-container {
2478 | display: none;
2479 | }
2480 | .floating-btn {
2481 | position: fixed;
2482 | bottom: 20px;
2483 | right: 20px;
2484 | background: green;
2485 | color: white;
2486 | border-radius: 50%;
2487 | width: 50px;
2488 | height: 50px;
2489 | display: flex;
2490 | align-items: center;
2491 | justify-content: center;
2492 | font-size: 24px;
2493 | cursor: pointer;
2494 | }
2495 | @media (max-width: 768px) {
2496 | .container {
2497 | flex-direction: column;
2498 | align-items: center;
2499 | }
2500 | .content-area,
2501 | .sidebar {
2502 | width: 90%;
2503 | }
2504 | }
2505 |
2506 | @media (max-width: 375px) {
2507 | .container {
2508 | flex-direction: column;
2509 | align-items: center;
2510 | }
2511 | .content-area,
2512 | .sidebar {
2513 | width: 100%;
2514 | }
2515 | }
2516 |
2517 | @media (max-width: 320px) {
2518 | .container {
2519 | flex-direction: column;
2520 | align-items: center;
2521 | }
2522 | .content-area,
2523 | .sidebar {
2524 | width: 100%;
2525 | }
2526 | }
2527 | """
2528 |
2529 | VIEW_CSS = \
2530 | """
2531 | body {
2532 | display: flex;
2533 | justify-content: center;
2534 | align-items: center;
2535 | height: 100vh;
2536 | margin: 0;
2537 | font-family: Arial, sans-serif;
2538 | background-color: #f4f4f4;
2539 | }
2540 | .container {
2541 | text-align: center;
2542 | background: rgb(225, 247, 238);
2543 | padding: 20px;
2544 | border-radius: 5px;
2545 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
2546 | }
2547 |
2548 | .container a {
2549 | display: block;
2550 | margin-bottom: 10px;
2551 | font-size: 16px;
2552 | text-decoration: none;
2553 | color: #007bff;
2554 | }
2555 | .container p {
2556 | margin: 5px 0;
2557 | font-size: 17px;
2558 | }
2559 | """
--------------------------------------------------------------------------------
/flask_quickstart_generator/env.py:
--------------------------------------------------------------------------------
1 | ENVIRONMENT_VARIABLE = \
2 | """
3 | ADMIN_USERNAME=admin
4 | ADMIN_PASSWORD=adminpass
5 | """
--------------------------------------------------------------------------------
/flask_quickstart_generator/forms.py:
--------------------------------------------------------------------------------
1 | SADASHBOARD_FORM = \
2 | """
3 | from flask_wtf import FlaskForm
4 | from wtforms.validators import DataRequired, Length
5 | from wtforms import SubmitField, StringField, PasswordField, EmailField
6 |
7 |
8 | class LoginForm(FlaskForm):
9 | email = EmailField(validators=[DataRequired()], render_kw={"placeholder": "Email"})
10 |
11 | username = StringField(
12 | validators=[DataRequired()], render_kw={"placeholder": "Username"}
13 | )
14 | password = PasswordField(
15 | validators=[DataRequired()], render_kw={"placeholder": "Password"}
16 | )
17 | login = SubmitField(label="Submit")
18 |
19 |
20 | class RoleForm(FlaskForm):
21 | # User Role
22 | role_name = StringField(label=
23 | "Role Name",
24 | validators=[DataRequired(), Length(min=3, max=50)],
25 | render_kw={"placeholder": "Enter Role Name/Number"},
26 | )
27 | submit = SubmitField("Add Role")
28 | """
29 |
30 |
31 | AUTHENTICATION_FORM = \
32 | """
33 | from flask_wtf import FlaskForm
34 | from my_demo_app.database.models import Role, User
35 | from wtforms import SubmitField, StringField, PasswordField, SelectField, EmailField
36 | from wtforms.validators import (
37 | Length,
38 | EqualTo,
39 | DataRequired,
40 | InputRequired,
41 | ValidationError,
42 | )
43 |
44 |
45 | class RegisterForm(FlaskForm):
46 | def __init__(self, *args, **kwargs):
47 | super().__init__(*args, **kwargs)
48 | # Fetch roles from the database dynamically
49 | self.user_role.choices = [("", "Select Role")] + [
50 | (role.name, role.name) for role in Role.query.all()
51 | ]
52 |
53 | username = StringField(
54 | validators=[DataRequired(), Length(min=4, max=15)],
55 | render_kw={"placeholder": "Username"},
56 | )
57 |
58 | email = EmailField(
59 | validators=[DataRequired(), Length(max=100)],
60 | render_kw={"placeholder": "Email"},
61 | )
62 |
63 | password = PasswordField(
64 | validators=[DataRequired(message="Enter password"), Length(min=4, max=15)],
65 | render_kw={"placeholder": "Password"},
66 | )
67 |
68 | confirm_password = PasswordField(
69 | validators=[
70 | InputRequired(),
71 | EqualTo("password", message="Password must match"),
72 | ],
73 | render_kw={"placeholder": "Repeat Password"},
74 | )
75 |
76 | user_role = SelectField(
77 | label="User Role",
78 | validators=[DataRequired()],
79 | coerce=str,
80 | default="",
81 | )
82 |
83 | register = SubmitField(label="Register")
84 |
85 | def validate_username(self, username):
86 | # Custom validation function to check for existing username.
87 | # Raises a ValidationError if the username already exists in the database.
88 | user = User.query.filter_by(username=username.data).first()
89 | if user:
90 | raise ValidationError(message="Username already exist!")
91 |
92 | def validate_email(self, email):
93 | # Custom validation function to check for existing email.
94 | # Raises a ValidationError if the email already exists in the database.
95 | user = User.query.filter_by(email=email.data).first()
96 | if user:
97 | raise ValidationError(message="Email already exists!")
98 |
99 | def validate_user_role(self, user_role):
100 | # Custom validation function to ensure a valid user role is selected.
101 | # Raises a ValidationError if the selected role is invalid or already exists.
102 | if user_role.data == "":
103 | raise ValidationError(message="Please select a valid user role.")
104 |
105 | existing_user = User.query.filter_by(user_role=user_role.data).first()
106 | if existing_user:
107 | raise ValidationError(message="User role already exists!")
108 |
109 |
110 | class LoginForm(FlaskForm):
111 | username = StringField(
112 | validators=[DataRequired()], render_kw={"placeholder": "Username"}
113 | )
114 |
115 | password = PasswordField(
116 | validators=[DataRequired()], render_kw={"placeholder": "Password"}
117 | )
118 |
119 | loginb_ = SubmitField(label="Submit")
120 | """
121 |
122 | SEARCH_FORM = \
123 | """
124 | from flask_wtf import FlaskForm
125 | from wtforms.validators import DataRequired
126 | from wtforms import SearchField, SubmitField
127 |
128 |
129 | class ProductSearchForm(FlaskForm):
130 | search_query = SearchField(validators=[DataRequired()], render_kw={"placeholder": "Search product name"})
131 | submit = SubmitField(label="Search")
132 | """
133 |
134 | ACCOUNT_SETTINGS_FORM = \
135 | """
136 | from flask_wtf import FlaskForm
137 | from flask_login import current_user
138 | from my_demo_app.database.models import User
139 | from flask_wtf.file import FileField, FileAllowed
140 | from wtforms import StringField, SubmitField, PasswordField, EmailField
141 | from wtforms.validators import DataRequired, ValidationError, Optional, Length, EqualTo
142 |
143 |
144 | class UpdateAccount(FlaskForm):
145 | email = EmailField(render_kw={"placeholder": "Email"})
146 |
147 | username = StringField(validators=[DataRequired()])
148 |
149 | picture = FileField(validators=[FileAllowed(["jpg", "png"])])
150 |
151 | password = PasswordField(
152 | validators=[Optional(), Length(min=4, max=15)],
153 | render_kw={"placeholder": "Password"},
154 | )
155 |
156 | confirm_password = PasswordField(
157 | validators=[Optional(), EqualTo("password", message="Passwords must match.")],
158 | render_kw={"placeholder": "Confirm_Password"},
159 | )
160 |
161 | submit = SubmitField(label="Update")
162 |
163 | def validate_username(self, username):
164 | if username.data != current_user.username:
165 | user = User.query.filter_by(username=username.data).first()
166 | if user:
167 | raise ValidationError(
168 | message="That username already exists! Please choose a different username."
169 | )
170 | """
171 |
172 | UPLOAD_FILES_FORM = \
173 | """
174 | from flask_wtf import FlaskForm
175 | from wtforms import FileField, SubmitField
176 | from flask_wtf.file import FileField, MultipleFileField, DataRequired
177 |
178 |
179 | class SingleFileUploadForm(FlaskForm):
180 | file = FileField(label="Select File", validators=[DataRequired()])
181 | submit = SubmitField("Submit")
182 |
183 |
184 | class MultipleFileUploadForm(FlaskForm):
185 | files_ = MultipleFileField(label="Select files", validators=[DataRequired()])
186 | submit = SubmitField(label="Submit")
187 | """
--------------------------------------------------------------------------------
/flask_quickstart_generator/html.py:
--------------------------------------------------------------------------------
1 | BASE_HTML = """
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {% block head %}
11 | {% block title %}Flask QuickStart{% endblock title %}
12 | {% endblock head %}
13 |
14 |
15 |
16 |
17 |
18 |
22 |
23 |
27 |
28 |
29 | {% block body %}
30 |
31 |
32 |
33 |
34 | {% with messages = get_flashed_messages(with_categories=True) %} {% if
35 | messages %} {% for category, message in messages %} {% if category ==
36 | "error" %}
37 |
38 |
39 |
40 | {{ message }}
41 |
42 |
43 | {% else %}
44 |
45 |
46 | {{ message }} {% endif %}{% endfor %}{% endif %}{% endwith %}
47 |
48 |
49 |
50 |
51 | {% block content %}{% endblock content %} {% endblock body %}
52 |
53 |
54 | {% block footer %}
55 |
60 | {% endblock footer %}
61 |
62 |
63 | """
64 |
65 | FLASH_CUSTOM_HTML = """
66 |
67 |
68 |
69 |
70 | {% with messages = get_flashed_messages(with_categories=True) %} {% if
71 | messages %} {% for category, message in messages %} {% if category ==
72 | "error" %}
73 |
74 |
75 |
76 | {{ message }}
77 |
78 |
79 | {% else %}
80 |
81 |
82 | {{ message }} {% endif %}{% endfor %}{% endif %}{% endwith %}
83 |
84 |
85 | """
86 |
87 | AUTHENTICATION_REGISTER_HTML = """
88 | {% extends "update_account.html" %}
89 |
90 | {% block title %}Create Account{% endblock title %}
91 |
92 | {% block user_profile %} {% endblock user_profile %}
93 |
94 | {% block password %} {% endblock password %}
95 |
96 |
97 | {% block form %}
98 |
117 | {% endblock form %}
118 | """
119 |
120 | AUTHENTICATION_LOGIN_HTML = """
121 | {% extends "base.html" %}
122 |
123 |
124 | {% block head %}
125 | {% block title %}Flask QuickStart{% endblock title %}
126 |
127 |
128 |
129 |
133 | {% endblock head %}
134 |
135 |
136 | {% block body %} {% block content %}
137 |
138 |
139 |
140 |
143 |
144 |
Flask QuickStart
145 |
172 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | {% endblock content %} {% endblock body %}
183 | """
184 |
185 | DEMO_HTML_TEMPLATES = """
186 | {% extends "base.html" %}
187 |
188 | {% block head %}
189 | {% block title %}Flask QuickStart{% endblock title %}
190 |
191 |
192 | {% endblock head %}
193 |
194 | {% block body %}
195 | {% block content %}
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 | {% endblock content %}
204 |
205 | {% endblock body %}
206 | """
207 |
208 | SADMIN_LOGIN_SECURE = """
209 |
210 |
211 |
212 |
213 |
214 | Flask Admin
215 |
221 |
222 |
226 |
227 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
Flask administration
241 |
Build scalable web applications with ease
242 |
243 |
256 |
257 |
258 | {% include 'flash_message.html' %}
259 |
260 |
261 |
266 |
267 |
268 |
269 |
270 |
271 | """
272 |
273 | SADASHBOARD_SECURE = """
274 |
275 |
276 |
277 |
278 |
279 | Flask Administration
280 |
281 |
287 |
288 |
294 |
295 |
299 |
300 |
304 |
305 |
306 | {% block top_navbar %}
307 |
349 | {% endblock top_navbar %}
350 |
351 |
352 | {% block sidebar %}
353 |
508 | {% endblock sidebar %}
509 |
510 |
511 | {% block content_header %}
512 |
522 | {% endblock content_header %} {% block dashboard_container %}
523 |
524 |
525 |
Total Users Registered: 1,240
526 |
587.00
527 |
12.5% increase in the last week
528 |
529 |
530 |
System Uptime: 99.97%
531 |
6,084
532 |
Updated hourly
533 |
534 |
535 |
Pending Admin Tasks: 14
536 |
250.00
537 |
28.6% of total tasks pending this week
538 |
539 |
540 |
Total Revenue: $12,360
541 |
7,360.00
542 |
45.3% of quarterly revenue goal
543 |
544 |
545 | {% endblock dashboard_container %} {% block table_container %}
546 |
547 |
Admin Activity Logs
548 |
549 |
550 | {% block search %}
551 |
561 | {% endblock search%}
562 |
563 |
564 |
565 | {% block table %}
566 |
567 |
568 | USER ID |
569 | ACTION |
570 | TIMESTAMP |
571 | STATUS |
572 | ROLE |
573 | IP ADDRESS |
574 | SESSION DURATION |
575 |
576 |
577 |
578 |
579 | #U1001 |
580 | Login |
581 | 2025-03-19 08:45:12 |
582 | Success |
583 | Admin |
584 | 192.168.1.15 |
585 | 45 minutes |
586 |
587 |
588 |
589 | #U1002 |
590 | File Upload |
591 | 2025-03-19 09:20:43 |
592 | Success |
593 | User |
594 | 192.168.1.18 |
595 | 30 minutes |
596 |
597 |
598 |
599 | #U1003 |
600 | Password Change |
601 | 2025-03-19 09:55:11 |
602 | Failed |
603 | Admin |
604 | 192.168.1.22 |
605 | 15 minutes |
606 |
607 |
608 |
609 |
610 |
611 | Total Records: 3
612 | |
613 |
614 |
615 | {% endblock table %}
616 |
617 |
618 |
619 | {% block table_footer %}
620 |
624 |
625 |
628 |
631 |
632 | {% endblock table_footer %}
633 |
634 |
635 | {% endblock table_container %} {% block bottom_table %}
636 |
637 |
638 |
648 |
649 |
650 |
651 |
652 |
653 | ITEM |
654 | QUANTITY |
655 | PRICE |
656 | AMOUNT |
657 |
658 |
659 |
660 |
661 | Consultation Report |
662 | 3 reports |
663 | $150.00 |
664 | $450.00 |
665 |
666 |
667 | Software License |
668 | 2 licenses |
669 | $300.00 |
670 | $600.00 |
671 |
672 |
673 | Cloud Hosting Plan (6 Months) |
674 | 1 subscription |
675 | $500.00 |
676 | $500.00 |
677 |
678 |
679 | Marketing Campaign (3 Weeks) |
680 | 2 campaigns |
681 | $1,000.00 |
682 | $2,000.00 |
683 |
684 |
685 | Custom Website Design |
686 | 1 project |
687 | $2,500.00 |
688 | $2,500.00 |
689 |
690 |
691 | Cybersecurity Audit |
692 | 1 audit |
693 | $800.00 |
694 | $800.00 |
695 |
696 |
697 |
698 |
699 |
700 | Total Amount: $6,850.00
701 | |
702 |
703 |
704 |
705 |
1
706 |
2
707 |
708 | {% endblock bottom_table %} {% block line_charts %}
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 | {% endblock line_charts %} {% block pie_charts %}
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 | {% endblock pie_charts %} {% block bar_charts %}
733 |
Live data
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 | {% endblock bar_charts %} {% block profile_overview %}
747 |
751 | {% endblock profile_overview %}
752 |
753 |
754 | {% block profile_container %}
755 |
756 | {% block left_pannel %}
757 |
758 |

759 |
{{ current_user.username }}
760 |
{{ current_user.email }}
761 |
123-123-555
762 |
763 |
764 |
774 | {% endblock left_pannel %}
775 |
776 |
777 |
778 | {% block user_profile_overview %}
779 |
About Me
780 |
781 |
Full Name: {{ current_user.username }}
782 |
Phone: 123-123-555
783 |
Email: {{ current_user.email }}
784 |
785 | {% endblock user_profile_overview %}
786 |
787 |
788 |
789 | {% block edit_form_field %}
790 |
Edit Profile
791 |
860 | {% endblock edit_form_field %}
861 |
862 |
863 |
864 | {% block create_new_user %}
865 |
Create New User
866 |
910 | {% endblock create_new_user %}
911 |
912 |
913 |
914 | {% block reset_password %}
915 |
Password Reset
916 |
949 | {% endblock reset_password %}
950 |
951 | {% endblock profile_container %}
952 |
953 |
954 |
955 |
956 |
957 | {% include 'flash_message.html' %}
958 |
959 |
964 |
965 |
966 |
967 |
968 |
969 |
970 | """
971 |
972 |
973 | BAD_REQUESTS_HTML = """
974 | {% extends "base.html" %} {% block head %}
975 | {% block title %}Bad Request{% endblock title %}
976 |
977 |
978 | {% endblock head %} {% block body %} {% block content %}
979 |
980 |
981 |
982 |
400
983 |
Bad Request
984 |
Sorry, your request couldn't be processed.
985 |
986 |
987 |
988 |
989 | {% endblock content %}
990 |
991 | {% endblock body %}
992 | """
993 |
994 |
995 | CONTENT_TOO_LARGE_HTML = """
996 | {% extends "base.html" %} {% block head %}
997 | {% block title %}File Too Large{% endblock title %}
998 |
999 |
1000 | {% endblock head %} {% block body %} {% block content %}
1001 |
1002 |
1003 |
1004 |
1007 |
TOO LARGE
1008 |
1009 |
413
1010 |
Payload Too Large
1011 |
The file or request data you tried to upload exceeds the allowed limit.
1012 |
1013 |
1014 |
1015 | {% endblock content %}
1016 |
1017 | {% endblock body %}
1018 | """
1019 |
1020 |
1021 | FORBIDDEN_HTML = """
1022 | {% extends "base.html" %} {% block head %}
1023 | {% block title %}Forbidden{% endblock title %}
1024 |
1025 |
1026 | {% endblock head %} {% block body %} {% block content %}
1027 |
1028 |
1029 |
1030 |
1031 | 403
1032 |
1033 |
1034 |
Forbidden
1035 |
You don't have permission to access this page.
1036 |
1037 |
1038 |
1039 | {% endblock content %}
1040 |
1041 | {% endblock body %}
1042 | """
1043 |
1044 |
1045 | INTERNAL_SERVER_HTML = """
1046 | {% extends "base.html" %} {% block head %}
1047 | {% block title %}Internal Server Error{% endblock title %}
1048 |
1049 | {% endblock head %} {% block body %}
1050 |
1051 |
1052 | {% block content %}
1053 |
1054 |
1055 |
500
1056 |
Internal Server Error
1057 |
Something went wrong on our end. Please try again later.
1058 |
1059 |
1060 |
1061 |
1062 |
1063 | {% endblock content %}
1064 |
1065 | {% endblock body %}
1066 |
1067 | """
1068 |
1069 |
1070 | MAINTAINANCE_HTML = """
1071 | {% extends "base.html" %} {% block head %}
1072 | {% block title %}503 Service Unavailable{% endblock title %}
1073 |
1074 | {% endblock head %}
1075 |
1076 |
1077 | {% block body %} {% block content %}
1078 |
1079 |
1082 |
503
1083 |
Service Unavailable
1084 |
1085 | Our service is temporarily unavailable. We're working on it and will be back
1086 | soon!
1087 |
1088 |
1089 |
1090 |
1091 | {% endblock content %}
1092 |
1093 | {% endblock body %}
1094 | """
1095 |
1096 |
1097 | NOT_FOUND_HTML = """
1098 |
1099 |
1100 | {% extends "base.html" %} {% block head %}
1101 | {% block title %}404 Page Not Found{% endblock title %}
1102 |
1103 |
1104 | {% endblock head %} {% block body %} {% block content %}
1105 |
1106 |
1107 |
1108 |
404
1109 |
Oops! The page you're looking for doesn't exist.
1110 |
1111 |
1112 |
1113 |
1114 |
1115 | {% endblock content %}
1116 |
1117 | {% endblock body %}
1118 | """
1119 |
1120 |
1121 | TOO_MANAY_REQUEST_HTML = """
1122 | {% extends "base.html" %} {% block head %}
1123 | {% block title %}Too Many Requests{% endblock title %}
1124 |
1125 |
1126 | {% endblock head %} {% block body %} {% block content %}
1127 |
1128 |
1129 |
1130 |
429
1131 |
Too Many Requests
1132 |
You've sent too many requests in a short time. Please wait.
1133 |
1134 |
10
1135 |
seconds remaining
1136 |
1137 |
1138 |
1139 |
1140 |
1141 |
1142 | {% endblock content %}
1143 |
1144 | {% endblock body %}
1145 | """
1146 |
1147 |
1148 | UNAUTHORIZED_HTML = """
1149 | {% extends "base.html" %} {% block head %}
1150 | {% block title %}Unauthorized{% endblock title %}
1151 |
1152 |
1153 | {% endblock head %} {% block body %} {% block content %}
1154 |
1155 |
1156 |
1160 |
401
1161 |
Unauthorized
1162 |
Oops! You don't have permission to access this page.
1163 |
1164 |
1165 |
1166 | {% endblock content %}
1167 |
1168 | {% endblock body %}
1169 | """
1170 |
1171 |
1172 | ACCOUNT_SETTING_FORM_HTML =\
1173 | """
1174 | {% extends "base.html" %}
1175 |
1176 | {% block head %}
1177 | {% block title %}Account Update{% endblock title %}
1178 |
1179 |
1183 |
1184 |
1188 | {% endblock head %}
1189 |
1190 |
1191 | {% block body %} {% block content %}
1192 |
1193 |
1194 |
1228 |
1229 |
1230 |
1231 |
1232 |
Profile Overview
1233 |
Name: {{ current_user.username }}
1234 |
Email: {{ current_user.email }}
1235 |
Phone: 123-123-555
1236 |
1237 |
1238 | {% block form %}
1239 |
1240 |
Reset Password
1241 |
1267 |
1268 | {% endblock form %}
1269 |
1270 |
1271 |
1272 | {% include 'flash_message.html' %}
1273 |
1274 | {% endblock content %}
1275 |
1276 | {% endblock body %}
1277 | """
1278 |
1279 |
1280 | PROFILE_UPDATE_HTML =\
1281 | """
1282 |
1283 | {% extends "sadashboard_secure.html" %}
1284 |
1285 |
1286 | {% block top_navbar %}
1287 | {{ super() }}
1288 | {% endblock top_navbar %}
1289 |
1290 | {% block sidebar %}
1291 | {{ super() }}
1292 | {% endblock sidebar %}
1293 |
1294 | {% block access_control %}
1295 | {{ super() }}
1296 | {% endblock access_control %}
1297 |
1298 | {% block admin_tools %}
1299 | {{ super() }}
1300 | {% endblock admin_tools %}
1301 |
1302 | {% block user_account %}
1303 | {{ super() }}
1304 | {% endblock user_account %}
1305 |
1306 | {% block report_analitics %}
1307 | {{ super() }}
1308 | {% endblock report_analitics %}
1309 |
1310 | {% block system_health %}
1311 | {{ super() }}
1312 | {% endblock system_health %}
1313 |
1314 | {% block database_mgt %}
1315 | {{ super() }}
1316 | {% endblock database_mgt %}
1317 |
1318 | {% block notification %}
1319 | {{ super() }}
1320 | {% endblock notification %}
1321 |
1322 | {% block api_mgt %}
1323 | {{ super() }}
1324 | {% endblock api_mgt %}
1325 |
1326 | {% block settings %}
1327 | {{ super() }}
1328 | {% endblock settings %}
1329 |
1330 | {% block content_header %}
1331 |
1332 | {% endblock content_header %}
1333 |
1334 | {% block dashboard_container %}
1335 |
1336 | {% endblock dashboard_container %}
1337 |
1338 | {% block table_container %}
1339 |
1340 | {% endblock table_container %}
1341 |
1342 | {% block search %}
1343 |
1344 | {% endblock search%}
1345 |
1346 | {% block table %}
1347 |
1348 | {% endblock table %}
1349 |
1350 | {% block table_footer %}
1351 |
1352 | {% endblock table_footer %}
1353 |
1354 | {% block bottom_table %}
1355 |
1356 | {% endblock bottom_table %}
1357 |
1358 | {% block line_charts %}
1359 | {{ super()}}
1360 | {% endblock line_charts %}
1361 |
1362 | {% block pie_charts %}
1363 | {{ super()}}
1364 | {% endblock pie_charts %}
1365 |
1366 | {% block bar_charts %}
1367 | {{ super()}}
1368 | {% endblock bar_charts %}
1369 |
1370 | {% block profile_overview %}
1371 | {{ super() }}
1372 | {% endblock profile_overview %}
1373 |
1374 | {% block profile_container %}
1375 | {{ super() }}
1376 | {% endblock profile_container %}
1377 |
1378 | {% block left_pannel %}
1379 | {{ super() }}
1380 | {% endblock left_pannel %}
1381 |
1382 | {% block user_profile_overview %}
1383 | {{ super() }}
1384 | {% endblock user_profile_overview %}
1385 |
1386 | {% block edit_form_field %}
1387 | {{ super() }}
1388 | {% endblock edit_form_field %}
1389 |
1390 | {% block create_new_user %}
1391 | {{ super() }}
1392 | {% endblock create_new_user %}
1393 |
1394 | {% block reset_password %}
1395 | {{ super() }}
1396 | {% endblock reset_password %}
1397 | """
1398 |
1399 |
1400 | VIEW_HTML =\
1401 | """
1402 | {% extends "base.html" %}
1403 |
1404 | {% block head %}
1405 | {% block title %}Flask QuickStart{% endblock title %}
1406 |
1407 |
1408 |
1409 | {% endblock head %} {% block body %} {% block content %}
1410 |
1411 |
1412 |
1413 |
Run your application and click on the link below:
1414 |
Login Here
1415 |
Default Email: admin@example.com
1416 |
Default Username: admin
1417 |
Default Password: adminpass
1418 |
1419 |
1420 |
1421 | {% endblock content %}
1422 |
1423 | {% endblock body %}
1424 | """
1425 |
1426 |
1427 | USER_ROLE_HTML =\
1428 | """
1429 | {% extends "signup.html" %}
1430 |
1431 | {% block title %}User Role{% endblock title %}
1432 |
1433 | {% block user_profile %} {% endblock user_profile %}
1434 |
1435 | {% block password %} {% endblock password %}
1436 |
1437 | {% block account %}
1438 |
1439 | Add User Role
1440 |
1441 | {% endblock account %}
1442 |
1443 |
1444 | {% block form %}
1445 |
1455 | {% endblock form %}
1456 | """
--------------------------------------------------------------------------------
/flask_quickstart_generator/js.py:
--------------------------------------------------------------------------------
1 | SADASHBOARD_JS = """
2 |
3 | // USER PROFILE DROPDOWN
4 | const userProfile = document.querySelector(".user-profile");
5 | userProfile.addEventListener("click", () => {
6 | userProfile.classList.toggle("open");
7 | });
8 | document.addEventListener("click", function (e) {
9 | if (userProfile && !userProfile.contains(e.target)) {
10 | userProfile.classList.remove("open");
11 | }
12 | });
13 |
14 | // TOGGLE SIDEBAR
15 | const sidebar = document.querySelector(".sidebar");
16 | const toggleSidebar = document.querySelector(".toggle-sidebar");
17 | const mainSection = document.querySelector(".main-section");
18 |
19 | // Toggle sidebar when clicking the toggle button
20 | toggleSidebar.addEventListener("click", () => {
21 | sidebar.classList.toggle("closed");
22 | mainSection.classList.toggle("sidebar-closed");
23 | });
24 |
25 | // Close sidebar when clicking outside of it
26 | document.addEventListener("click", (e) => {
27 | const isClickInsideSidebar = sidebar.contains(e.target);
28 | const isClickOnToggle = toggleSidebar.contains(e.target);
29 |
30 | if (!isClickInsideSidebar && !isClickOnToggle) {
31 | sidebar.classList.remove("closed");
32 | mainSection.classList.remove("sidebar-closed");
33 | }
34 | });
35 |
36 | // SUBMENU TOGGLE (Accordion behavior)
37 | const submenuParents = document.querySelectorAll(".has-submenu");
38 | submenuParents.forEach((parentLi) => {
39 | const menuLink = parentLi.querySelector("a");
40 | menuLink.addEventListener("click", (e) => {
41 | e.preventDefault();
42 | // Close other submenus
43 | submenuParents.forEach((otherLi) => {
44 | if (otherLi !== parentLi) {
45 | otherLi.classList.remove("open");
46 | }
47 | });
48 | // Toggle current submenu
49 | parentLi.classList.toggle("open");
50 | });
51 | });
52 |
53 | // On page load, check localStorage and apply the theme
54 | if (localStorage.getItem("theme") === "dark") {
55 | document.body.classList.add("dark-theme");
56 | } else {
57 | document.body.classList.remove("dark-theme");
58 | }
59 |
60 | const themeToggle = document.getElementById("theme-toggle");
61 | themeToggle.addEventListener("click", (e) => {
62 | e.preventDefault();
63 | document.body.classList.toggle("dark-theme");
64 | // Save the selection in localStorage
65 | if (document.body.classList.contains("dark-theme")) {
66 | localStorage.setItem("theme", "dark");
67 | } else {
68 | localStorage.setItem("theme", "light");
69 | }
70 | });
71 |
72 | //FIRST SECTION: 3 SIDE-BY-SIDE LINE CHARTS
73 | const ctx1 = document.getElementById("chart1").getContext("2d");
74 | const chart1 = new Chart(ctx1, {
75 | type: "line",
76 | data: {
77 | labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], // Months or timeline periods
78 | datasets: [
79 | {
80 | label: "Project Milestones (Count)",
81 | data: [2, 3, 5, 7, 9, 11], // Number of milestones completed each month
82 | backgroundColor: "rgba(75, 192, 192, 0.2)",
83 | borderColor: "rgba(75, 192, 192, 1)",
84 | fill: true,
85 | tension: 0.2,
86 | },
87 | ],
88 | },
89 | options: {
90 | scales: {
91 | y: { beginAtZero: true },
92 | },
93 | plugins: {
94 | title: {
95 | display: true,
96 | text: "Project Milestone Progress",
97 | },
98 | },
99 | },
100 | });
101 |
102 | // 2) Team Task Completion Summary
103 | const ctx2 = document.getElementById("chart2").getContext("2d");
104 | const chart2 = new Chart(ctx2, {
105 | type: "line",
106 | data: {
107 | labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], // Months or periods
108 | datasets: [
109 | {
110 | label: "Team Task Completion (Count)", // Professional label
111 | data: [8, 12, 18, 22, 30, 35], // Number of tasks completed each month
112 | backgroundColor: "rgba(153, 102, 255, 0.2)",
113 | borderColor: "rgba(153, 102, 255, 1)",
114 | fill: true,
115 | tension: 0.2,
116 | },
117 | ],
118 | },
119 | options: {
120 | scales: {
121 | y: { beginAtZero: true },
122 | },
123 | plugins: {
124 | title: {
125 | display: true,
126 | text: "Team Task Completion Progress", // Added professional chart title
127 | },
128 | },
129 | },
130 | });
131 |
132 | // 3) Task Completion Efficiency
133 | const ctx3 = document.getElementById("chart3").getContext("2d");
134 | const chart3 = new Chart(ctx3, {
135 | type: "line",
136 | data: {
137 | labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"], // Time periods (months)
138 | datasets: [
139 | {
140 | label: "Task Completion Efficiency (%)", // New professional label
141 | data: [85, 80, 90, 87, 92, 95], // Task completion efficiency in percentage
142 | backgroundColor: "rgba(153, 102, 255, 0.2)",
143 | borderColor: "rgba(153, 102, 255, 1)",
144 | fill: true,
145 | tension: 0.2,
146 | },
147 | ],
148 | },
149 | options: {
150 | scales: {
151 | y: { beginAtZero: true, max: 100 }, // Efficiency percentage with max set to 100
152 | },
153 | plugins: {
154 | title: {
155 | display: true,
156 | text: "Task Completion Efficiency Over Time", // Professional chart title
157 | },
158 | },
159 | },
160 | });
161 |
162 | //THREE PIE CHARTS, EACH IN ITS OWN BOX Left//
163 | const ctxPieA = document.getElementById("pieChartA").getContext("2d");
164 | const pieChartA = new Chart(ctxPieA, {
165 | type: "pie",
166 | data: {
167 | labels: ["One", "Two", "Three"],
168 | datasets: [
169 | {
170 | label: "Sample A",
171 | data: [30, 40, 30],
172 | backgroundColor: ["#36a2eb", "#ff6384", "#ffce56"],
173 | },
174 | ],
175 | },
176 | });
177 |
178 | // Middle
179 | const ctxPieB = document.getElementById("pieChartB").getContext("2d");
180 | const pieChartB = new Chart(ctxPieB, {
181 | type: "pie",
182 | data: {
183 | labels: ["Alpha", "Beta", "Gamma"],
184 | datasets: [
185 | {
186 | label: "Sample B",
187 | data: [25, 50, 25],
188 | backgroundColor: ["#4bc0c0", "#9966ff", "#ff9f40"],
189 | },
190 | ],
191 | },
192 | });
193 |
194 | // Right
195 | const ctxPieC = document.getElementById("pieChartC").getContext("2d");
196 | const pieChartC = new Chart(ctxPieC, {
197 | type: "pie",
198 | data: {
199 | labels: ["Red", "Green", "Blue"],
200 | datasets: [
201 | {
202 | label: "Sample C",
203 | data: [33, 33, 34],
204 | backgroundColor: ["#ff6384", "#36a2eb", "#4bc0c0"],
205 | },
206 | ],
207 | },
208 | });
209 |
210 | // === THIRD SECTION: 3 SIDE-BY-SIDE BAR CHARTS ===
211 | // Left Bar
212 | const ctxBarA = document.getElementById("barChartA").getContext("2d");
213 | const barChartA = new Chart(ctxBarA, {
214 | type: "bar",
215 | data: {
216 | labels: ["Mon", "Tue", "Wed", "Thu", "Fri"],
217 | datasets: [
218 | {
219 | label: "Bar A",
220 | data: [5, 8, 3, 10, 6],
221 | backgroundColor: "rgba(255, 99, 132, 0.2)",
222 | borderColor: "rgba(255, 99, 132, 1)",
223 | borderWidth: 1,
224 | },
225 | ],
226 | },
227 | options: {
228 | scales: {
229 | y: { beginAtZero: true },
230 | },
231 | },
232 | });
233 |
234 | // Middle Bar
235 | const ctxBarB = document.getElementById("barChartB").getContext("2d");
236 | const barChartB = new Chart(ctxBarB, {
237 | type: "bar",
238 | data: {
239 | labels: ["Mon", "Tue", "Wed", "Thu", "Fri"],
240 | datasets: [
241 | {
242 | label: "Bar B",
243 | data: [7, 2, 9, 4, 12],
244 | backgroundColor: "rgba(54, 162, 235, 0.2)",
245 | borderColor: "rgba(54, 162, 235, 1)",
246 | borderWidth: 1,
247 | },
248 | ],
249 | },
250 | options: {
251 | scales: {
252 | y: { beginAtZero: true },
253 | },
254 | },
255 | });
256 |
257 | // Right Bar
258 | const ctxBarC = document.getElementById("barChartC").getContext("2d");
259 | const barChartC = new Chart(ctxBarC, {
260 | type: "bar",
261 | data: {
262 | labels: ["Mon", "Tue", "Wed", "Thu", "Fri"],
263 | datasets: [
264 | {
265 | label: "Bar C",
266 | data: [10, 4, 6, 8, 9],
267 | backgroundColor: "rgba(255, 206, 86, 0.2)",
268 | borderColor: "rgba(255, 206, 86, 1)",
269 | borderWidth: 1,
270 | },
271 | ],
272 | },
273 | options: {
274 | scales: {
275 | y: { beginAtZero: true },
276 | },
277 | },
278 | });
279 |
280 | // Select the links and sections
281 | const profileOverviewLink = document.getElementById("profile-overview-link");
282 | const editProfileLink = document.getElementById("edit-profile-link");
283 | const createAccountLink = document.getElementById("create-account-link");
284 | const passwordResetLink = document.getElementById("password-reset-link");
285 |
286 | const profileOverviewSection = document.getElementById("profile-overview");
287 | const editProfileSection = document.getElementById("edit-profile");
288 | const createAccountSection = document.getElementById("create-account");
289 | const passwordResetSection = document.getElementById("password-reset");
290 |
291 | // Function to hide all sections
292 | function hideAllSections() {
293 | profileOverviewSection.classList.remove("active-section");
294 | editProfileSection.classList.remove("active-section");
295 | createAccountSection.classList.remove("active-section");
296 | passwordResetSection.classList.remove("active-section");
297 | }
298 |
299 | // Function to remove 'active-link' from all links
300 | function deactivateAllLinks() {
301 | profileOverviewLink.classList.remove("active-link");
302 | editProfileLink.classList.remove("active-link");
303 | createAccountLink.classList.remove("active-link");
304 | passwordResetLink.classList.remove("active-link");
305 | }
306 |
307 | // Event listeners for links
308 | profileOverviewLink.addEventListener("click", () => {
309 | hideAllSections();
310 | deactivateAllLinks();
311 | profileOverviewSection.classList.add("active-section");
312 | profileOverviewLink.classList.add("active-link");
313 | });
314 |
315 | editProfileLink.addEventListener("click", () => {
316 | hideAllSections();
317 | deactivateAllLinks();
318 | editProfileSection.classList.add("active-section");
319 | editProfileLink.classList.add("active-link");
320 | });
321 |
322 | createAccountLink.addEventListener("click", () => {
323 | hideAllSections();
324 | deactivateAllLinks();
325 | createAccountSection.classList.add("active-section");
326 | createAccountLink.classList.add("active-link");
327 | });
328 |
329 | passwordResetLink.addEventListener("click", () => {
330 | hideAllSections();
331 | deactivateAllLinks();
332 | passwordResetSection.classList.add("active-section");
333 | passwordResetLink.classList.add("active-link");
334 | });
335 |
336 | // Initially, show the profile overview section
337 | profileOverviewSection.classList.add("active-section");
338 | profileOverviewLink.classList.add("active-link");
339 |
340 | // Show the button when the user scrolls down 20px from the top of the page
341 | window.onscroll = function () {
342 | scrollFunction();
343 | };
344 |
345 | function scrollFunction() {
346 | const scrollTopBtn = document.getElementById("scrollTopBtn");
347 | if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
348 | scrollTopBtn.style.display = "block";
349 | } else {
350 | scrollTopBtn.style.display = "none";
351 | }
352 | }
353 |
354 | // Smoothly scroll to the top of the page when the user clicks the button
355 | function scrollToTop() {
356 | window.scrollTo({ top: 0, behavior: "smooth" });
357 | }
358 |
359 | // Get the button
360 | let scrollToTopBtn = document.getElementById("scrollTopBtn");
361 |
362 | // Add click event listener to the button
363 | scrollToTopBtn.addEventListener("click", function () {
364 | window.scrollTo({
365 | top: 0,
366 | behavior: "smooth", // Smooth scrolling effect
367 | });
368 | });
369 | """
370 |
371 |
372 | LOG_JS = """
373 | const darkModeToggle = document.getElementById("dark-mode-toggle");
374 | const mainContainer = document.querySelector(".container-fluid");
375 |
376 | // Check localStorage for dark mode preference on page load
377 | window.addEventListener("DOMContentLoaded", () => {
378 | const darkMode = localStorage.getItem("darkMode");
379 |
380 | // Apply dark mode if it was enabled
381 | if (darkMode === "enabled") {
382 | mainContainer.classList.add("dark-mode");
383 | darkModeToggle.checked = true; // Set the toggle to ON
384 | }
385 | });
386 |
387 | // Add event listener to the toggle switch
388 | darkModeToggle.addEventListener("change", () => {
389 | if (darkModeToggle.checked) {
390 | mainContainer.classList.add("dark-mode");
391 | localStorage.setItem("darkMode", "enabled"); // Save dark mode state to localStorage
392 | } else {
393 | mainContainer.classList.remove("dark-mode");
394 | localStorage.setItem("darkMode", "disabled"); // Save light mode state to localStorage
395 | }
396 | });
397 | """
398 |
399 | FLASH_DOM_REMOVE = """
400 | document.addEventListener("DOMContentLoaded", function () {
401 | // Find all the flash messages
402 | const flashMessages = document.querySelectorAll(".flash--message .alert");
403 |
404 | // For each flash message, set a timeout to remove it after the animation completes
405 | flashMessages.forEach(function (message) {
406 | // Get the total animation time (slideIn + fadeOut)
407 | const totalAnimationTime = 4000; // 4 seconds (or match your fade-out duration)
408 |
409 | // Set a timeout to remove the message from the DOM after the animation ends
410 | setTimeout(function () {
411 | message.remove(); // This will remove the element from the DOM
412 | }, totalAnimationTime);
413 | });
414 | });
415 | """
416 |
417 | ERROR_PAGES = """
418 |
419 | document.addEventListener("DOMContentLoaded", function () {
420 | // Select the button by id
421 | const goBackButton = document.getElementById("go-back-btn");
422 |
423 | // Attach the click event handler to the button
424 | if (goBackButton) {
425 | goBackButton.addEventListener("click", function () {
426 | window.history.back();
427 | });
428 | }
429 | });
430 |
431 | // 500 server
432 | document.addEventListener("DOMContentLoaded", function () {
433 | // Select the button by its id
434 | const refreshBtn = document.getElementById("server_refresh-btn");
435 |
436 | // Attach the click event if the button exists
437 | if (refreshBtn) {
438 | refreshBtn.addEventListener("click", function () {
439 | location.reload(); // Refresh the page on click
440 | });
441 | }
442 | });
443 | """
444 |
445 | MAINTAINANCE_JS = """
446 | document.addEventListener('DOMContentLoaded', function () {
447 | // Auto-refresh every 10 minutes (600000 milliseconds)
448 | setInterval(function () {
449 | window.location.reload();
450 | }, 60000);
451 |
452 | // Bind click event to the reload button (if it exists) to allow immediate reload
453 | const reloadBtn = document.getElementById('main-reload-btn');
454 | if (reloadBtn) {
455 | reloadBtn.addEventListener('click', function () {
456 | window.location.reload();
457 | });
458 | }
459 | });
460 | """
461 |
462 | NOT_FOUND_JS = """
463 |
464 | document.addEventListener("DOMContentLoaded", function () {
465 | // Select the button by id
466 | const goBackButton = document.getElementById("go-back-btn");
467 |
468 | // Attach the click event handler to the button
469 | if (goBackButton) {
470 | goBackButton.addEventListener("click", function () {
471 | window.history.back();
472 | });
473 | }
474 | });
475 | """
476 |
477 | TOO_MANY_JS =\
478 | """
479 |
480 | document.addEventListener("DOMContentLoaded", function () {
481 | // Countdown Timer Logic
482 | let timeLeft = 40; // You can adjust the time
483 | const countdownElement = document.getElementById("countdown");
484 | const retryButton = document.getElementById("retry-btn");
485 |
486 | if (countdownElement) {
487 | const countdown = setInterval(() => {
488 | timeLeft--;
489 | countdownElement.innerText = timeLeft;
490 |
491 | if (timeLeft <= 0) {
492 | clearInterval(countdown);
493 | document.querySelector(".timer").innerHTML =
494 | "You can try again now.
";
495 | }
496 | }, 1000);
497 | } else {
498 | console.error("Element with id 'countdown' not found.");
499 | }
500 |
501 | // Retry button logic
502 | if (retryButton) {
503 | retryButton.addEventListener("click", function () {
504 | location.reload(); // Reloads the page after countdown finishes
505 | });
506 | }
507 | });
508 | """
509 |
510 |
511 | PROFILE_AC =\
512 | """
513 | // document.addEventListener("DOMContentLoaded", function () {
514 | // function showSection(section) {
515 | // document.getElementById("profile").style.display = "none";
516 | // document.getElementById("create").style.display = "none";
517 | // document.getElementById("reset").style.display = "none";
518 | // document.getElementById(section).style.display = "block";
519 | // }
520 |
521 | // document.getElementById("profileLink").addEventListener("click", function () {
522 | // showSection("profile");
523 | // });
524 |
525 | // document.getElementById("createLink").addEventListener("click", function () {
526 | // showSection("create");
527 | // });
528 |
529 | // document.getElementById("resetLink").addEventListener("click", function () {
530 | // showSection("reset");
531 | // });
532 |
533 | // // Show profile overview by default
534 | // showSection("profile");
535 | // });
536 |
537 | document.addEventListener("DOMContentLoaded", function () {
538 | function showSection(section) {
539 | let profile = document.getElementById("profile");
540 | let create = document.getElementById("create");
541 | let reset = document.getElementById("reset");
542 |
543 | if (profile) profile.style.display = "none";
544 | if (create) create.style.display = "none";
545 | if (reset) reset.style.display = "none";
546 |
547 | let target = document.getElementById(section);
548 | if (target) target.style.display = "block";
549 | }
550 |
551 | const profileLink = document.getElementById("profileLink");
552 | const createLink = document.getElementById("createLink");
553 | const resetLink = document.getElementById("resetLink");
554 |
555 | if (profileLink) {
556 | profileLink.addEventListener("click", function () {
557 | showSection("profile");
558 | });
559 | }
560 |
561 | if (createLink) {
562 | createLink.addEventListener("click", function () {
563 | showSection("create");
564 | });
565 | }
566 |
567 | if (resetLink) {
568 | resetLink.addEventListener("click", function () {
569 | showSection("reset");
570 | });
571 | }
572 |
573 | // Default section to show (if available)
574 | if (document.getElementById("profile")) {
575 | showSection("profile");
576 | } else if (document.getElementById("create")) {
577 | showSection("create");
578 | } else if (document.getElementById("reset")) {
579 | showSection("reset");
580 | }
581 | });
582 | """
583 |
584 | AUTO_LOGOUT_ = """
585 | let timeout;
586 |
587 | function resetTimer() {
588 | clearTimeout(timeout);
589 | timeout = setTimeout(logout, 180000); // 3 minutes
590 | }
591 |
592 | function logout() {
593 | const logoutUrl = document.body.getAttribute("data-logout-url");
594 | if (logoutUrl) {
595 | window.location.href = logoutUrl;
596 | }
597 | }
598 |
599 | // Start/reset on common activity
600 | document.addEventListener('DOMContentLoaded', resetTimer);
601 | document.addEventListener('mousemove', resetTimer);
602 | document.addEventListener('keypress', resetTimer);
603 | document.addEventListener('click', resetTimer);
604 | document.addEventListener('scroll', resetTimer);
605 | """
--------------------------------------------------------------------------------
/flask_quickstart_generator/main.py:
--------------------------------------------------------------------------------
1 | from .cli import main
2 |
3 |
4 | main()
5 |
--------------------------------------------------------------------------------
/flask_quickstart_generator/model.py:
--------------------------------------------------------------------------------
1 | USER_MODEL = """
2 |
3 | from sqlalchemy.sql import func
4 | from flask_login import UserMixin
5 | from my_demo_app import db, login_manager
6 |
7 |
8 | # This function is used by Flask-Login to load the user from the database.
9 | @login_manager.user_loader
10 | def load_user(user_id):
11 | return User.query.get(int(user_id))
12 |
13 |
14 | class User(db.Model, UserMixin): # We assume a User can be a Teacher, Administrator
15 | __tablename__ = "users"
16 |
17 | id = db.Column(db.Integer, primary_key=True, unique=True)
18 | email = db.Column(db.String(255), unique=True, nullable=True)
19 | username = db.Column(db.String(length=150), nullable=True)
20 | password = db.Column(db.String(length=255), nullable=False)
21 | user_role = db.Column(db.String(length=100), nullable=False)
22 | user_profile = db.Column(
23 | db.String(length=200), nullable=False, default="default.jpg"
24 | )
25 | date_created = db.Column(db.Date, default=func.current_date(), nullable=False)
26 |
27 | __table_args__ = (
28 | db.Index('user_idx', 'date_created'),
29 | )
30 |
31 | def __repr__(self) -> str:
32 | return f"User('{self.username}')"
33 |
34 |
35 | class Role(db.Model):
36 | __tablename__ = "roles"
37 |
38 | id = db.Column(db.Integer, primary_key=True)
39 | name = db.Column(db.String(100), unique=True, nullable=False)
40 |
41 | def __repr__(self):
42 | return f"Role('{self.name}')"
43 | """
44 |
--------------------------------------------------------------------------------
/flask_quickstart_generator/routes_.py:
--------------------------------------------------------------------------------
1 | GITIGNORE = \
2 | """
3 | *.db
4 | venv/
5 | config/.venv
6 | __pycache__
7 | /__pycache__
8 | **/*/__pycache__
9 | """
10 |
11 | ANSI_COLORS_ = \
12 | """
13 | import sys
14 | import os
15 |
16 |
17 | # CUSTOM_COLORS = {
18 | # "GREEN": "\033[92m",
19 | # "RED": "\033[91m",
20 | # "YELLOW": "\033[93m",
21 | # "RESET": "\033[0m",
22 | # }
23 |
24 | # Custom ANSI escape codes
25 | CUSTOM_COLORS = {
26 | "GREEN": "\033[92m",
27 | "RED": "\033[91m",
28 | "YELLOW": "\033[93m",
29 | "RESET": "\033[0m",
30 | }
31 |
32 | # No color fallback
33 | NO_COLOR = {
34 | "GREEN": "",
35 | "RED": "",
36 | "YELLOW": "",
37 | "RESET": "",
38 | }
39 |
40 | def get_color_support():
41 | # Dynamically returns the best color support depending on the environment.
42 | # - VS Code => colorama
43 | # - Sublime Text or others => ANSI (custom)
44 | # - Fallback => no color
45 | try:
46 | # If output is a real terminal
47 | if sys.stdout.isatty():
48 | # VS Code specific: has full ANSI + colorama support
49 | if "VSCODE_PID" in os.environ:
50 | from colorama import init, Fore, Style
51 |
52 | init(autoreset=True)
53 | return {
54 | "GREEN": Fore.GREEN,
55 | "RED": Fore.RED,
56 | "YELLOW": Fore.YELLOW,
57 | "RESET": Style.RESET_ALL,
58 | }
59 |
60 | # Other terminals that support ANSI (like CMD, Linux)
61 | return CUSTOM_COLORS
62 |
63 | except Exception:
64 | pass
65 |
66 | # Fallback (Sublime Text or broken shell)
67 | return NO_COLOR
68 | """
69 |
70 |
71 | APP_STARTUP = \
72 | """
73 | from waitress import serve
74 | from my_demo_app import db, app, flask_db_init
75 | from my_demo_app.super_admin.routes import seed_super_admin
76 |
77 |
78 | if __name__ == "__main__":
79 | with app.app_context():
80 | db.create_all()
81 | # db.drop_all()
82 | flask_db_init() #This function creates and initiates the `db migration folder`
83 | seed_super_admin()
84 | app.run(host="0.0.0.0",debug=True, port=5000)
85 | # serve(app, host='0.0.0.0', port=5000, threads=100) # Use Waitress to serve the app
86 | """
87 |
88 | VIEW_TEMPLATE_CODE = \
89 | """
90 | from flask_login import login_user
91 | from flask import flash, redirect, url_for
92 | from my_demo_app import limiter, bcrypt, db
93 | from my_demo_app.database.models import User
94 | from flask import render_template, Blueprint
95 | from my_demo_app.authentication.form import LoginForm
96 |
97 |
98 | view = Blueprint("view", __name__, template_folder="templates", static_folder="static")
99 |
100 | @view.route("/")
101 | @view.route("/home_page")
102 | @limiter.limit("5 per minute", override_defaults=True)
103 | def home_page():
104 |
105 | return render_template("index.html")
106 |
107 | # @view.route("/")
108 | # @view.route("/home_page", methods=["GET", "POST"])
109 | # @limiter.limit("5 per minute", override_defaults=True)
110 | # def home_page():
111 | # form = LoginForm()
112 | # if form.validate_on_submit():
113 | # user = User.query.filter_by(username=form.username.data).first()
114 | # if user and bcrypt.check_password_hash(user.password, form.password.data):
115 | # if user.user_role == "SuperAdmin":
116 | # login_user(user)
117 | # flash(message=f"Login Successfuly {user.username}", category="success")
118 | # return redirect(url_for(endpoint="admin_controller.secure_dashboard"))
119 | # elif user.user_role == "NormalUser":
120 | # login_user(user)
121 | # flash(message=f"Login Successfuly {user.username}", category="success")
122 | # return redirect(url_for(endpoint="admin_controller.secure_dashboard"))
123 | # else:
124 | # flash(message="Invalid Username or Password", category="error")
125 | # return render_template("index.html", form=form)
126 | """
127 |
128 | SEARCH_TEMPLATE_CODE = \
129 | """
130 | from sqlalchemy import or_
131 | from my_demo_app import limiter
132 | from .form import ProductSearchForm
133 | from my_demo_app.database.models import User
134 | from flask import render_template, Blueprint, flash
135 |
136 |
137 | search_ = Blueprint(
138 | "search_", __name__, template_folder="templates", static_folder="static"
139 | )
140 |
141 | @search_.route("/search")
142 | @limiter.limit("5 per minute", override_defaults=True)
143 | def search_item():
144 | form = ProductSearchForm()
145 | search_results = [] # Intialize early to avoid UnboundLocalError
146 |
147 | if form.validate_on_submit():
148 | search_query = form.search_query.data
149 | search_results = User.query.filter(
150 | User.username.ilike(f"%{search_query}%")
151 | ).all()
152 | if search_results:
153 | flash(
154 | message=f"{len(search_query)}Data found for query: {search_query}",
155 | category="success",
156 | )
157 | else:
158 | flash(message=f"Data not found for query: {search_query}", category="error")
159 | return render_template("item_search.html", form=form, search_results=search_results)
160 | """
161 |
162 | ERROR_HANDLER_TEMPLATE_CODE = \
163 | """
164 | from my_demo_app import app
165 | from flask import session
166 | from http import HTTPStatus
167 | from flask import render_template, Blueprint, flash
168 |
169 |
170 | errors_ = Blueprint(
171 | "errors_", __name__, template_folder="templates", static_folder="static"
172 | )
173 |
174 | @errors_.app_errorhandler(400)
175 | def bad_request(error):
176 | return render_template("bad_request.html"), HTTPStatus.BAD_REQUEST
177 |
178 |
179 | @errors_.app_errorhandler(401)
180 | def un_authorized(error):
181 | return render_template("unauthorized.html"), HTTPStatus.UNAUTHORIZED
182 |
183 |
184 | @errors_.app_errorhandler(403)
185 | def forbidden_error(error):
186 | return render_template("forbidden.html"), HTTPStatus.FORBIDDEN
187 |
188 |
189 | @errors_.app_errorhandler(404)
190 | def not_found_error(error):
191 | return render_template("not_found.html"), HTTPStatus.NOT_FOUND
192 |
193 |
194 | @errors_.app_errorhandler(413)
195 | def payload_too_large_error(error):
196 | return render_template("payload_data.html"), HTTPStatus.CONTENT_TOO_LARGE
197 |
198 |
199 | @errors_.app_errorhandler(429)
200 | def too_many_requests_error(error):
201 | flash(
202 | message="Your request is too much, try again in a few minutes", category="error"
203 | )
204 | return render_template("too_many_requests.html"), HTTPStatus.TOO_MANY_REQUESTS
205 |
206 |
207 | @errors_.app_errorhandler(500)
208 | def internal_server_error(error):
209 | return (
210 | render_template("internal_server.html"),
211 | HTTPStatus.INTERNAL_SERVER_ERROR,
212 | )
213 |
214 |
215 | @errors_.app_errorhandler(ValueError)
216 | def value_error(error):
217 | error_message = session.pop("error_message", None)
218 | if error_message:
219 | app.logger.error(error_message)
220 | else:
221 | app.logger.error(f"Error occurred: {error}")
222 | return render_template("invalid_path.html")
223 |
224 |
225 | def maintainance():
226 | # Your logic here
227 | pass
228 |
229 |
230 | @errors_.app_errorhandler(503)
231 | def app_maintenance_mode(error): # Optional prefix for consistency
232 | if maintainance: # Replace with your logic to check maintenance mode
233 | return render_template("maintenance.html"), HTTPStatus.SERVICE_UNAVAILABLE
234 | # Code to handle other 503 errors (optional)
235 | return None # Fallback for non-maintenance related 503 errors
236 | """
237 |
238 | AUTHENTICATION_TEMPLATE_CODE = \
239 | """
240 | from .form import RegisterForm
241 | from my_demo_app.database.models import Role, User
242 | from my_demo_app import limiter, bcrypt, db, logging
243 | from flask_login import current_user, logout_user, login_required
244 | from flask import render_template, Blueprint, flash, redirect, url_for
245 |
246 |
247 | authent_ = Blueprint(
248 | "authent_", __name__, template_folder="templates", static_folder="static"
249 | )
250 |
251 |
252 | @authent_.route(rule="/reguser", methods=["GET", "POST"])
253 | @limiter.limit("5 per minute", override_defaults=True)
254 | @login_required
255 | def secure_register():
256 | # Check if any roles exist
257 | if not Role.query.first():
258 | flash(
259 | message="No user roles found! Please add roles before registering users.",
260 | category="error",
261 | )
262 | return redirect(url_for("super_admin_secure.add_role")) # Redirect admin to add roles
263 |
264 | form = RegisterForm()
265 | if form.validate_on_submit():
266 | try:
267 | pw_hash = bcrypt.generate_password_hash(form.password.data).decode("utf-8")
268 | user = User(
269 | username=form.username.data,
270 | email=form.email.data,
271 | password=pw_hash,
272 | user_role=form.user_role.data,
273 | )
274 | db.session.add(user)
275 | db.session.commit()
276 | flash(
277 | message=f"Account Created Successfully {user.username}",
278 | category="success",
279 | )
280 | return redirect(url_for(endpoint="super_admin_secure.secure_adashboard"))
281 | except Exception as e:
282 | db.session.rollback()
283 | logging.error(msg=f"Error adding new user to the database {e}")
284 | flash(
285 | message="There was a problem adding data to the database",
286 | category="error",
287 | )
288 |
289 | error_messages = [
290 | err_msg for err_msg_list in form.errors.values() for err_msg in err_msg_list
291 | ]
292 | if error_messages:
293 | flash(
294 | message=f"Registration Unsuccessful! {', '.join(error_messages)}",
295 | category="error",
296 | )
297 | image_file = url_for("static", filename="media/" + current_user.user_profile)
298 | return render_template("signup.html", form=form, image_file=image_file)
299 |
300 |
301 | # @authent_.route("/logout")
302 | # def user_logout():
303 | # logout_user()
304 | # flash(message="Logout Successfully", category="success")
305 | # return redirect(url_for("view.home_page"))
306 | """
307 |
308 | ACCOUNT_UTILS = \
309 | """
310 | import os
311 | import secrets
312 | from my_demo_app import app
313 | from PIL import Image
314 | from flask import Blueprint
315 | from flask_login import current_user
316 | from werkzeug.utils import secure_filename
317 |
318 |
319 | img_utils = Blueprint(
320 | "img_utils", __name__, template_folder="templates", static_folder="static"
321 | )
322 |
323 |
324 | def save_user_picture(form_picture):
325 | # Generate a random hexadecimal name
326 | random_hex = secrets.token_hex(8)
327 | _, f_ext = os.path.splitext(form_picture.filename)
328 |
329 | # Sanitize the filename using secure_filename
330 | picture_fn = secure_filename(random_hex + f_ext)
331 |
332 | # Define the picture path
333 | picture_path = os.path.join(
334 | app.root_path, "static/media", picture_fn
335 | )
336 |
337 | # Remove the existing user profile picture if it exists
338 | if os.path.exists(
339 | os.path.join(app.root_path, "static/media", current_user.user_profile)):
340 | os.remove(os.path.join(app.root_path, "static/media", current_user.user_profile))
341 |
342 | # Resize the image
343 | output_size = (750, 750)
344 | i = Image.open(form_picture)
345 | i.thumbnail(output_size)
346 | i.save(picture_path)
347 |
348 | return picture_fn
349 | """
350 |
351 | ACCOUNT_SETTINGS_TEMPLATE_CODE = \
352 | """
353 | from my_demo_app import db, bcrypt
354 | from .form import UpdateAccount
355 | from my_demo_app.media_utils.utils import save_user_picture
356 | from flask_login import current_user, login_required
357 | from flask import render_template, Blueprint, request, redirect, flash, url_for
358 |
359 |
360 | account_ = Blueprint(
361 | "account_", __name__, template_folder="templates", static_folder="static"
362 | )
363 |
364 |
365 | @account_.route(rule=f"/account", methods=["GET", "POST"])
366 | @login_required
367 | def secure_account_update():
368 | form = UpdateAccount()
369 |
370 | if form.validate_on_submit():
371 | # Handle profile picture update if provided
372 | if form.picture.data:
373 | picture_file = save_user_picture(form.picture.data)
374 | current_user.user_profile = picture_file
375 |
376 | # Handle username update
377 | current_user.email = form.email.data
378 | current_user.username = form.username.data
379 |
380 | # Handle password update only if a new password is provided
381 | if form.password.data:
382 | # Check if passwords match
383 | if form.password.data == form.confirm_password.data:
384 | # Hash the new password and update it
385 | hashed_password = bcrypt.generate_password_hash(
386 | form.password.data
387 | ).decode("utf-8")
388 | current_user.password = hashed_password
389 | else:
390 | flash(message="Passwords do not match.", category="error")
391 | return redirect(url_for(endpoint="account_.secure_account_update"))
392 |
393 | # Commit the changes to the database
394 | db.session.commit()
395 | flash(message="Your account has been updated!", category="success")
396 | return redirect(url_for(endpoint="super_admin_secure.secure_adashboard"))
397 |
398 | # If validation fails, flash the errors for debugging
399 | if form.errors:
400 | for field, errors in form.errors.items():
401 | for error in errors:
402 | flash(message=f"Error in {field}: {error}", category="error")
403 |
404 | # Prepopulate fields for GET request
405 | elif request.method == "GET":
406 | form.email.data = current_user.email
407 | form.username.data = current_user.username
408 |
409 | image_file = url_for("static", filename="media/" + current_user.user_profile)
410 |
411 | return render_template(
412 | template_name_or_list="update_account.html", image_file=image_file, form=form
413 | )
414 | """
415 |
416 | FILES_UPLOAD_TEMPLATE_CODE = \
417 | """
418 | import os
419 | from my_demo_app import limiter, app, cache
420 | from werkzeug.utils import secure_filename
421 | from .form import MultipleFileUploadForm, SingleFileUploadForm
422 | from flask import render_template, Blueprint, redirect, url_for
423 |
424 |
425 | file_upload_ = Blueprint(
426 | "file_upload_", __name__, template_folder="templates", static_folder="static"
427 | )
428 |
429 |
430 | # Route for handling single file upload
431 | @file_upload_.route("/singleupload", methods=["GET", "POST"])
432 | @limiter.limit("10 per minute", override_defaults=True)
433 | def secure_single_upload():
434 | # Create form instance
435 | form = SingleFileUploadForm()
436 |
437 | # Check if form is submitted and valid
438 | if form.validate_on_submit():
439 | # Access the uploaded file
440 | file = form.file.data
441 |
442 | # Check if a file was uploaded
443 | if file:
444 | # Get file extension and lowercase it
445 | extension = os.path.splitext(file.filename)[1].lower()
446 |
447 | # Generate secure file path
448 | file_path = os.path.join(
449 | app.config["UPLOAD_FOLDER"], secure_filename(file.filename)
450 | )
451 |
452 | # Check if file extension is allowed
453 | if extension not in app.config["ALLOWED_EXTENSIONS"]:
454 | return "File is not an allowed type"
455 |
456 | # Save the file to the upload folder
457 | file.save(file_path)
458 |
459 | # Invalidate the cache for the homepage
460 | with app.app_context():
461 | cache.delete("home_page")
462 |
463 | # Redirect to registration page after successful upload
464 | return redirect(url_for("view.home_page"))
465 | else:
466 | # Inform user if no file was selected
467 | return "No file selected"
468 |
469 | # Render the template with the form
470 | return render_template("file_upload.html", form=form)
471 |
472 |
473 | # Route for handling multiple file uploads
474 | # @file_upload_.route("/multiple_upload", methods=["GET", "POST"])
475 | # @limiter.limit("10 per minute", override_defaults=True)
476 | # def secure_multiple_upload():
477 | # # Create form instance
478 | # form = MultipleFileUploadForm()
479 |
480 | # # Check if form is submitted and valid
481 | # if form.validate_on_submit():
482 | # # Access uploaded files as a list
483 | # files = form.files_.data
484 |
485 | # # Check if any files were uploaded
486 | # if files:
487 | # # Loop through each uploaded file
488 | # for file in files:
489 | # # Get file extension and lowercase it
490 | # extension = os.path.splitext(file.filename)[1].lower()
491 |
492 | # # Generate secure file path
493 | # file_path = os.path.join(
494 | # app.config["UPLOAD_FOLDER"], secure_filename(file.filename)
495 | # )
496 |
497 | # # Check if file extension is allowed
498 | # if extension not in app.config["ALLOWED_EXTENSIONS"]:
499 | # return "File is not an allowed type"
500 |
501 | # # Save the file to the upload folder
502 | # file.save(file_path)
503 |
504 | # # Redirect to registration page after successful upload
505 | # return redirect(url_for("view.home_page"))
506 | # else:
507 | # # Inform user if no files were selected
508 | # return "No files selected"
509 |
510 | # # Render the template with the form
511 | # return render_template("file_upload.html", form=form)
512 | """
513 |
514 | ADMIN_TEMPLATE_CODE = \
515 | """
516 | import secrets
517 | from flask import render_template, Blueprint
518 |
519 |
520 | admin_controller = Blueprint(
521 | "admin_controller", __name__, template_folder="templates", static_folder="static"
522 | )
523 |
524 |
525 | # @admin_controller.route(f"/{secrets.token_urlsafe()}")
526 | @admin_controller.route("/controller", methods=["GET"])
527 | def controller():
528 | return render_template("controller.html")
529 | """
530 |
531 | SUPER_ADMIN_DASHBOARD_ = \
532 | """
533 | import os
534 | from my_demo_app import limiter
535 | from dotenv import load_dotenv
536 | from .form import LoginForm, RoleForm
537 | from my_demo_app import db, bcrypt, app, logging
538 | from my_demo_app.database.models import User, Role
539 | from flask_login import current_user, login_required, login_user, logout_user
540 | from flask import render_template, session, url_for, flash, redirect, Blueprint
541 |
542 |
543 | super_admin_secure = Blueprint(
544 | import_name=__name__,
545 | name="super_admin_secure",
546 | template_folder="templates",
547 | static_folder="static",
548 | )
549 |
550 |
551 | load_dotenv(dotenv_path="my_demo_app/config/.env")
552 | admin_username = os.getenv("ADMIN_USERNAME")
553 | admin_password = os.getenv("ADMIN_PASSWORD")
554 |
555 |
556 | def seed_super_admin():
557 | # Check if the admin user already exists
558 | existing_admin = User.query.filter_by(email="admin@example.com").first()
559 | if existing_admin:
560 | # Admin user already exists, no need to add again
561 | return
562 |
563 | # If the admin doesn't exist, create it
564 | admin = User(
565 | username=admin_username,
566 | password=bcrypt.generate_password_hash(admin_password).decode("utf-8"),
567 | user_role="SuperUser",
568 | email="admin@example.com",
569 | user_profile="default.jpg",
570 | )
571 | db.session.add(admin)
572 | db.session.commit()
573 |
574 |
575 | @super_admin_secure.route(rule="/superlogin", methods=["GET", "POST"])
576 | @limiter.limit(limit_value="5 per minute", override_defaults=True)
577 | def secure_superlogin():
578 | form = LoginForm()
579 | if form.validate_on_submit():
580 | user = User.query.filter_by(email=form.email.data).first()
581 | if user and bcrypt.check_password_hash(user.password, form.password.data):
582 | login_user(user)
583 | flash(message=f"Login Successfuly {user.username}", category="success")
584 | return redirect(url_for(endpoint="super_admin_secure.secure_adashboard"))
585 | else:
586 | flash(message="Invalid Username or Password", category="error")
587 | return render_template("salogin_secure.html", form=form)
588 |
589 |
590 | @super_admin_secure.route(rule="/secure_adashboard", methods=["GET", "POST"])
591 | @limiter.limit(limit_value="5 per minute", override_defaults=True)
592 | @login_required
593 | def secure_adashboard():
594 | image_file = url_for("static", filename="media/" + current_user.user_profile)
595 | return render_template("sadashboard_secure.html", image_file=image_file)
596 |
597 |
598 | @super_admin_secure.route(rule="/user_role", methods=["GET", "POST"])
599 | @login_required
600 | def add_role():
601 | form = RoleForm()
602 | if form.validate_on_submit():
603 | try:
604 | # Check if the role already exists before adding it
605 | existing_role = Role.query.filter_by(name=form.role_name.data).first()
606 | if existing_role:
607 | flash(message="Role already exists! choose different role", category="error")
608 | return redirect(url_for("super_admin_secure.add_role"))
609 |
610 | # Create a new role
611 | roles = Role(name=form.role_name.data)
612 | db.session.add(roles)
613 | db.session.commit()
614 |
615 | flash(message="User role added successfully", category="success")
616 | return redirect(url_for(endpoint="super_admin_secure.secure_adashboard"))
617 |
618 | except Exception as e:
619 | db.session.rollback()
620 | logging.error(f"Error adding User Roles: {str(e)}")
621 | flash(
622 | message="There was a problem adding User Role to the database",
623 | category="error",
624 | )
625 |
626 | # If form errors exist, flash the error messages
627 | if form.errors:
628 | error_message = [
629 | err_msg for err_msg_list in form.errors.values() for err_msg in err_msg_list
630 | ]
631 | flash(
632 | message=f"There was a problem with the form submission: {'. '.join(error_message)}",
633 | category="error",
634 | )
635 | image_file = url_for("static", filename="media/" + current_user.user_profile)
636 | return render_template("user_role.html", form=form, image_file=image_file)
637 |
638 |
639 |
640 | @super_admin_secure.route("/logout")
641 | def user_logout():
642 | logout_user() # Logs out the user
643 | flash(message="Logout Successfully", category="success")
644 | return redirect(url_for("super_admin_secure.secure_superlogin"))
645 | """
646 |
647 | CACHING_CONSTANT = \
648 | """
649 | from flask import Blueprint
650 |
651 |
652 | app_cache = Blueprint("app_cache", __name__)
653 |
654 |
655 | # Constants for minutes
656 | ONE_MINUTE = 60 # 1 minute in seconds
657 | TWO_MINUTES = 2 * ONE_MINUTE # 2 minutes in seconds
658 | THREE_MINUTES = 3 * ONE_MINUTE # 3 minutes in seconds
659 | FOUR_MINUTES = 4 * ONE_MINUTE # 4 minutes in seconds
660 | SIX_MINUTES = 6 * ONE_MINUTE # 6 minutes in seconds
661 | SEVEN_MINUTES = 7 * ONE_MINUTE # 7 minutes in seconds
662 | EIGHT_MINUTES = 8 * ONE_MINUTE # 8 minutes in seconds
663 | NINE_MINUTES = 9 * ONE_MINUTE # 9 minutes in seconds
664 | TEN_MINUTES = 10 * ONE_MINUTE # 10 minutes in seconds
665 | ELEVEN_MINUTES = 11 * ONE_MINUTE # 11 minutes in seconds
666 | TWELVE_MINUTES = 12 * ONE_MINUTE # 12 minutes in seconds
667 | THIRTEEN_MINUTES = 13 * ONE_MINUTE # 13 minutes in seconds
668 | FOURTEEN_MINUTES = 14 * ONE_MINUTE # 14 minutes in seconds
669 |
670 |
671 | # Constants for days
672 | ONE_DAY = 24 * 60 * 60 # 1 day in seconds
673 | TWO_DAYS = 2 * ONE_DAY # 2 days in seconds
674 | THREE_DAYS = 3 * ONE_DAY # 3 days in seconds
675 | FOUR_DAYS = 4 * ONE_DAY # 4 days in seconds
676 | FIVE_DAYS = 5 * ONE_DAY # 5 days in seconds
677 | SIX_DAYS = 6 * ONE_DAY # 6 days in seconds
678 | SEVEN_DAYS = ONE_DAY * 7 # 1 week in seconds
679 | """
--------------------------------------------------------------------------------
/flask_quickstart_generator/settings.py:
--------------------------------------------------------------------------------
1 | APP_SETTINGS = """
2 |
3 | import os
4 | import secrets
5 | import logging
6 | from flask import session
7 | from flask_babel import Babel
8 | from datetime import timedelta
9 | from flask_bcrypt import Bcrypt
10 | from flask_caching import Cache
11 | from colorama import Fore, Style
12 | from flask_limiter import Limiter
13 | from flask_migrate import Migrate
14 | from flask_session import Session
15 | from .ansi_ import get_color_support
16 | from flask_wtf.csrf import CSRFProtect
17 | from flask_sqlalchemy import SQLAlchemy
18 | from flask_limiter.util import get_remote_address
19 | from flask import Flask, request, redirect, url_for
20 | from flask_login import login_manager, LoginManager
21 |
22 |
23 | # Create a Flask application instance
24 | app = Flask(__name__)
25 |
26 |
27 | # SECURITY WARNING: keep the secret key used in production secret!
28 | # Use a secure environment variable to store the secret key.
29 | # You can use the 'secrets' module (available in Python 3.6+) to generate a secret key:
30 | SECRET_KEY = "flask-insecure-c#y(k55srf&7q^i@mi+f*tw_%ll$^w@#cd1=fwa6&8tr^2qwv1"
31 | secret_key = secrets.token_urlsafe(20) # Generate a secure secret key
32 |
33 |
34 | # Define the database path relative to the application directory
35 | APP_DATABASE = os.path.join(os.path.dirname(__file__), "database")
36 | DATABASE_PATH = os.path.join(APP_DATABASE, "Database.db")
37 |
38 |
39 | # Connecting to local postgress db
40 | POSTGRES_USER = "postgres"
41 | POSTGRES_PASSWORD = "your password"
42 | POSTGRES_DB = "database name"
43 | POSTGRES_HOST = "localhost"
44 | POSTGRES_PORT = "5432"
45 |
46 |
47 | # Configure Flask application settings
48 | app.config["SECRET_KEY"] = SECRET_KEY
49 | app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite:///{DATABASE_PATH}"
50 |
51 |
52 | # Please uncomment if you want to connect your Database to Postgress db.
53 | #app.config["SQLALCHEMY_DATABASE_URI"] = f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}" # Please uncomment if you want to use it.
54 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
55 | app.config["BABEL_DEFAULT_LOCALE"] = "en_US"
56 | app.config["BABEL_DEFAULT_TIMEZONE"] = "UTC"
57 |
58 |
59 | # Configure media files for file uploads
60 | app.config["MAX_CONTENT_LENGTH"] = 10 * 1024 * 1024 #10MB
61 | app.config["ALLOWED_EXTENSIONS"] = [".jpg", ".jpeg", ".png", ".pdf"]
62 | app.config["UPLOAD_FOLDER"] = os.path.abspath(os.path.join("my_demo_app", "static", "media"))
63 |
64 |
65 | # Configure Flask-Caching
66 | app.config["CACHE_TYPE"] = "simple" # You can use 'simple', 'redis', 'memcached'
67 | app.config["CACHE_DEFAULT_TIMEOUT"] = (
68 | 300 # Cache timeout in seconds (e.g., 300 seconds = 5 minutes)
69 | )
70 |
71 |
72 | # Initialize database, babel, cache, CSRF protection, and migration management instances
73 | babel = Babel(app)
74 | cache = Cache(app)
75 | cache.init_app(app)
76 | db = SQLAlchemy(app)
77 | bcrypt = Bcrypt(app)
78 | csrf = CSRFProtect(app) # Protect against Cross-Site Request Forgery (CSRF) attacks
79 | migrate = Migrate(app, db) # Database migration with Flask-Migrate
80 |
81 |
82 | # Initialize LoginManager for handling user login, current_user, and user_logout
83 | login_manager = LoginManager()
84 | login_manager.login_view = "super_admin_secure.secure_superlogin" # Redirect to homepage for login
85 | login_manager.init_app(app) # Initialize the app with LoginManager
86 | login_manager.login_message_category = "info" # Set the category for login messages
87 |
88 |
89 | # Session configuration
90 | app.config["SESSION_TYPE"] = "sqlalchemy" # Use SQLAlchemy backend
91 | app.config["SESSION_SQLALCHEMY"] = db # Reference SQLAlchemy instance
92 | app.config["SESSION_SQLALCHEMY_TABLE"] = "sessions" # Use the Session model
93 | app.config["SESSION_PERMANENT"] = True # Set to True for persistent sessions
94 | app.config["SESSION_USE_SIGNER"] = True
95 | app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=1)
96 | # app.config["SESSION_COOKIE_SAMESITE"] = 'None' Enable this config when using https:
97 | app.config["SESSION_COOKIE_HTTPONLY"] = True
98 | app.config["SESSION_COOKIE_SECURE"] = False
99 | Session(app) # Initialize Flask-Session extension
100 |
101 |
102 | # This makes it easier to pinpoint where things might be going wrong in the app
103 | logging.basicConfig(level=logging.DEBUG)
104 |
105 |
106 | # Flask-Limiter adds rate limiting to Flask applications (e.g limiting the number of request a client can send).
107 | limiter = Limiter(
108 | app=app,
109 | headers_enabled=True,
110 | storage_uri="memory://",
111 | key_func=get_remote_address,
112 | default_limits=["3000 per hour"],
113 | )
114 |
115 | colors = get_color_support()
116 |
117 | @app.before_request
118 | def middleware():
119 | if not request.path.startswith("/static"):
120 | print(f"{colors['GREEN']}middleware executes before: '{request.endpoint}' route.{colors['RESET']}")
121 | '''
122 | # Skip Processing for Dynamic Routes:
123 | # We check if the path starts with a slash (/) to handle potential invalid paths.
124 |
125 | # We use a generator expression and any to determine if any character after the first slash in the path (request.path[1:]) is non-alphanumeric. This identifies dynamic routes that likely use tokens or IDs.
126 |
127 | # If the path is identified as dynamic, the function returns None, effectively skipping URL processing for that route.
128 |
129 | # Canonicalization and Trailing Slash Removal:
130 | # If the path isn't a dynamic route, the middleware applies URL canonicalization (lowercase URLs) and trailing slash removal for static routes.
131 |
132 | # Benefits:
133 | # This approach avoids modifying URLs generated using secrets.token_urlsafe(), ensuring your dynamic routes maintain their integrity.
134 |
135 | # It keeps URL processing logic in a separate, reusable function for better organization.
136 | '''
137 | try:
138 | # Check for valid request path format (starts with a slash)
139 | if not request.path.startswith("/"):
140 | raise ValueError("Invalid request path format")
141 |
142 | # Skip processing for dynamic routes (non-alphanumeric characters)
143 | if any(not char.isalnum() for char in request.path[1:]):
144 | return None
145 |
146 | # URL Canonicalization: Redirect URLs with uppercase letters to lowercase
147 | if request.path != request.path.lower():
148 | return redirect(request.path.lower())
149 |
150 | # Remove trailing slashes except for root URL
151 | if request.path != "/" and request.path.endswith("/"):
152 | return redirect(request.path.rstrip("/"))
153 |
154 | except ValueError as e:
155 | app.logger.error(f"Middleware error: {e}, Request Path: {request.path}")
156 | session["error_message"] = (
157 | f"Middleware error: {e}, Request Path: {request.path}"
158 | )
159 | return redirect(url_for("errors_.value_error"))
160 |
161 | return None
162 |
163 |
164 | @app.after_request
165 | def security_headers(response):
166 | if not request.path.startswith("/static"):
167 | print(
168 | f"{colors['GREEN']}security header executes before: '{request.endpoint}' route. {colors['RESET']}"
169 | )
170 | # This middleware function sets the X-Frame-Options header to "DENY" to prevent clickjacking attacks.
171 |
172 | # Security Headers: Sets essential security headers to mitigate potential vulnerabilities:
173 |
174 | # Referrer-Policy: Limits referrer information leaks.
175 |
176 | # X-Content-Type-Options: Prevents MIME-sniffing.
177 |
178 | # Content-Security-Policy (Basic implementation): Provides a starting point for restricting resource loading.
179 |
180 | '''Content-Security-Policy (CSP) Header
181 | The Content-Security-Policy (CSP) header is a security feature that helps prevent various types of attacks, such as Cross-Site Scripting (XSS) and data injection attacks. It allows web developers to control which resources (e.g., scripts, stylesheets, fonts, images) the browser is allowed to load for a specific web page.
182 | '''
183 |
184 | # X-Frame-Options: # Prevent clickjacking
185 |
186 | response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
187 | # You can set response.headers to "no-referrer" to prevent any information leaking from your site.
188 |
189 | response.headers["X-Content-Type-Options"] = "nosniff"
190 |
191 | response.headers["Content-Security-Policy"] = (
192 | "default-src 'self'; "
193 |
194 | "script-src 'self' https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js https://cdn.jsdelivr.net/npm/chart.js; "
195 |
196 | "style-src 'self' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css; "
197 |
198 | "font-src 'self' https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/webfonts/;"
199 |
200 | "img-src 'self' https://images.pexels.com/photos/450035/pexels-photo-450035.jpeg blob: data:;"
201 | )
202 |
203 | #This ensures all communication with the server happens over HTTPS.
204 | # Prevent downgrade attacks: It prevents attackers from tricking users into using HTTP instead of HTTPS.
205 | response.headers["Strict-Transport-Security"] = (
206 | "max-age=31536000; includeSubDomains"
207 | )
208 |
209 | response.headers["X-Frame-Options"] = "DENY" # Prevent clickjacking
210 |
211 | # When executed the code below in a browser's console, it attempts to create an iframe pointing to http://127.0.0.1:5000/. However, because you've set the X-Frame-Options header to DENY, the browser will refuse to load your web page within an iframe, regardless of where it's hosted.
212 |
213 | '''var iframe = document.createElement('iframe');
214 | iframe.src = 'http://127.0.0.1:5000/';
215 | document.body.appendChild(iframe);'''
216 |
217 | return response
218 |
219 |
220 | # Safely manage the database session after every request
221 | @app.teardown_request
222 | def shutdown_session(exception=None):
223 | db.session.remove()
224 |
225 |
226 | def flask_db_init():
227 | # This function creates and initiates the `db migration` folder if it does not exist.
228 | migrations_folder = os.path.join(os.getcwd(), 'migrations')
229 | if not os.path.exists(migrations_folder):
230 | os.system("flask db init")
231 |
232 |
233 | # Import and register blueprint containing application routes
234 | from my_demo_app.views.routes import view
235 | from my_demo_app.search.routes import search_
236 | from my_demo_app.errors.routes import errors_
237 | from my_demo_app.uploads.routes import file_upload_
238 | from my_demo_app.media_utils.utils import img_utils
239 | from my_demo_app.admin.routes import admin_controller
240 | from my_demo_app.authentication.routes import authent_
241 | from my_demo_app.account_settings.routes import account_
242 | from my_demo_app.caching.cache_constant import app_cache
243 | from my_demo_app.super_admin.routes import super_admin_secure
244 |
245 |
246 | app.register_blueprint(view, url_prefix="/")
247 | app.register_blueprint(search_, url_prefix="/")
248 | app.register_blueprint(errors_, url_prefix="/")
249 | app.register_blueprint(authent_, url_prefix="/")
250 | app.register_blueprint(account_, url_prefix="/")
251 | app.register_blueprint(img_utils, url_prefix="/")
252 | app.register_blueprint(app_cache, url_prefix="/")
253 | app.register_blueprint(file_upload_, url_prefix="/")
254 | app.register_blueprint(admin_controller, url_prefix="/")
255 | app.register_blueprint(super_admin_secure, url_prefix="/")
256 | """
257 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | alembic==1.13.1
2 | Babel==2.15.0
3 | bcrypt==4.1.3
4 | blinker==1.8.2
5 | cachelib==0.9.0
6 | click==8.1.7
7 | colorama==0.4.6
8 | cssmin==0.2.0
9 | Deprecated==1.2.14
10 | Flask==3.0.3
11 | Flask-Assets==2.1.0
12 | flask-babel==4.0.0
13 | Flask-Bcrypt==1.0.1
14 | Flask-Caching==2.3.0
15 | Flask-Limiter==3.6.0
16 | Flask-Login==0.6.3
17 | Flask-Migrate==4.0.7
18 | Flask-Session==0.8.0
19 | Flask-SQLAlchemy==3.1.1
20 | Flask-WTF==1.2.1
21 | greenlet==3.0.3
22 | importlib_resources==6.4.0
23 | itsdangerous==2.2.0
24 | Jinja2==3.1.4
25 | jsmin==3.0.1
26 | limits==3.11.0
27 | Mako==1.3.3
28 | markdown-it-py==3.0.0
29 | MarkupSafe==2.1.5
30 | mdurl==0.1.2
31 | msgspec==0.18.6
32 | ordered-set==4.1.0
33 | packaging==24.0
34 | pillow==10.3.0
35 | Pygments==2.18.0
36 | pytz==2024.1
37 | rcssmin==1.1.2
38 | rich==13.7.1
39 | rjsmin==1.2.2
40 | SQLAlchemy==2.0.30
41 | typing_extensions==4.11.0
42 | webassets==2.0
43 | Werkzeug==3.0.3
44 | wrapt==1.16.0
45 | WTForms==3.1.2
46 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 |
3 |
4 | with open(file="Readme.md", encoding="utf-8", mode="r") as file:
5 | long_description = file.read()
6 |
7 |
8 | # Bump version:
9 | setup(
10 | name="flask_quickstart_generator",
11 | version="1.1.3",
12 | description="Flask Quickstart Generator streamlines Flask development by automatically generating a structured folder layout, expediting project setup.",
13 | long_description=long_description,
14 | long_description_content_type="text/markdown",
15 | author="Kennartech",
16 | author_email="kennartdev@gmail.com",
17 | maintainer="Kennartech.Dev",
18 | maintainer_email="kennartdev@gmail.com",
19 | url="https://ktecht.pythonanywhere.com/",
20 | include_package_data=True,
21 | package_data={"": ["requirements.txt", "Readme.md", "LICENSE.txt"]},
22 | packages=find_packages(),
23 | license="MIT",
24 | classifiers=[
25 | "Programming Language :: Python",
26 | "Programming Language :: Python :: 3",
27 | "Programming Language :: Python :: 3.10",
28 | "Programming Language :: Python :: 3.11",
29 | "Programming Language :: Python :: 3.12",
30 | "Programming Language :: Python :: 3.13",
31 | ],
32 | install_requires=[
33 | "Flask",
34 | "WTForms",
35 | "pillow",
36 | "psycopg2",
37 | "waitress",
38 | "Flask-WTF",
39 | "Flask-Login",
40 | "flask-babel",
41 | "Flask-Login",
42 | "Flask-Bcrypt",
43 | "python-dotenv",
44 | "Flask-Caching",
45 | "Flask-Limiter",
46 | "python-dotenv",
47 | "Flask-Migrate",
48 | "Flask-Session",
49 | "Flask-SQLAlchemy",
50 | ],
51 | entry_points={
52 | "console_scripts": [
53 | "flask-manage = flask_quickstart_generator:main",
54 | ],
55 | },
56 | )
57 |
58 |
--------------------------------------------------------------------------------