From 45bfd4b59273bd4b6ef9977739b61bdc9c0c224b Mon Sep 17 00:00:00 2001 From: pjpatil12 Date: Thu, 26 Feb 2026 17:08:49 +0530 Subject: [PATCH] added new of dashboard and log apply on routes --- app/__init__.py | 11 +- app/constants/http_status.py | 11 ++ app/constants/messages.py | 17 ++ app/errors/error_handlers.py | 66 +++++++ app/routes/dashboard.py | 11 +- app/routes/subcontractor_routes.py | 208 ++++++++------------- app/routes/user.py | 4 +- app/services/comparison_service.py | 0 app/services/logger_service.py | 82 ++++++++ app/templates/dashboard.html | 2 +- app/templates/subcontractor/add.html | 16 +- app/templates/subcontractor/edit.html | 4 +- app/templates/subcontractor/list.html | 17 +- app/templates/subcontractor_dashboard.html | 111 ++++++++++- app/utils/exceptions.py | 8 + app/utils/response_handler.py | 23 +++ 16 files changed, 427 insertions(+), 164 deletions(-) create mode 100644 app/constants/http_status.py create mode 100644 app/constants/messages.py create mode 100644 app/errors/error_handlers.py create mode 100644 app/services/comparison_service.py create mode 100644 app/services/logger_service.py create mode 100644 app/utils/exceptions.py create mode 100644 app/utils/response_handler.py diff --git a/app/__init__.py b/app/__init__.py index e5f62bb..52606e3 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,6 +1,7 @@ from flask import Flask, redirect, url_for from app.config import Config from app.services.db_service import db +from app.services.logger_service import LoggerService def create_app(): app = Flask(__name__) @@ -9,6 +10,9 @@ def create_app(): # Initialize extensions db.init_app(app) + # Initialize Logger + LoggerService.init_app(app) + # Register blueprints register_blueprints(app) # Register error handlers @@ -43,10 +47,15 @@ def register_blueprints(app): def register_error_handlers(app): + + from flask import current_app + @app.errorhandler(404) def page_not_found(e): + current_app.logger.warning("404 Page Not Found") return "Page Not Found", 404 @app.errorhandler(500) def internal_error(e): - return "Internal Server Error", 500 + current_app.logger.exception("500 Internal Server Error") + return "Internal Server Error", 500 \ No newline at end of file diff --git a/app/constants/http_status.py b/app/constants/http_status.py new file mode 100644 index 0000000..9859f7d --- /dev/null +++ b/app/constants/http_status.py @@ -0,0 +1,11 @@ +class HTTPStatus: + OK = 200 + CREATED = 201 + BAD_REQUEST = 400 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + METHOD_NOT_ALLOWED = 405 + CONFLICT = 409 + UNPROCESSABLE_ENTITY = 422 + INTERNAL_SERVER_ERROR = 500 \ No newline at end of file diff --git a/app/constants/messages.py b/app/constants/messages.py new file mode 100644 index 0000000..ada80e3 --- /dev/null +++ b/app/constants/messages.py @@ -0,0 +1,17 @@ +class SuccessMessage: + FETCHED = "Data fetched successfully" + CREATED = "Resource created successfully" + UPDATED = "Resource updated successfully" + DELETED = "Resource deleted successfully" + LOGIN = "Login successful" + LOGOUT = "Logout successful" + + +class ErrorMessage: + INVALID_REQUEST = "Invalid request data" + UNAUTHORIZED = "Unauthorized access" + FORBIDDEN = "Access forbidden" + NOT_FOUND = "Resource not found" + VALIDATION_FAILED = "Validation failed" + INTERNAL_ERROR = "Internal server error" + DUPLICATE_ENTRY = "Duplicate record found" diff --git a/app/errors/error_handlers.py b/app/errors/error_handlers.py new file mode 100644 index 0000000..aebb5b0 --- /dev/null +++ b/app/errors/error_handlers.py @@ -0,0 +1,66 @@ +from flask import request, render_template +from app.utils.response_handler import ResponseHandler +from app.utils.exceptions import APIException +from app.constants.http_status import HTTPStatus +from app.constants.messages import ErrorMessage +from app.services.db_service import db +import traceback + + +def register_error_handlers(app): + + # Custom API Exception + @app.errorhandler(APIException) + def handle_api_exception(e): + db.session.rollback() + + if request.path.startswith("/api"): + return ResponseHandler.error( + message=e.message, + errors=e.errors, + status_code=e.status_code + ) + + return render_template("errors/500.html"), e.status_code + + + # 404 + @app.errorhandler(404) + def handle_404(e): + if request.path.startswith("/api"): + return ResponseHandler.error( + message=ErrorMessage.NOT_FOUND, + status_code=HTTPStatus.NOT_FOUND + ) + + return render_template("errors/404.html"), 404 + + + # 500 + @app.errorhandler(500) + def handle_500(e): + db.session.rollback() + traceback.print_exc() + + if request.path.startswith("/api"): + return ResponseHandler.error( + message=ErrorMessage.INTERNAL_ERROR, + status_code=HTTPStatus.INTERNAL_SERVER_ERROR + ) + + return render_template("errors/500.html"), 500 + + + # Catch All + @app.errorhandler(Exception) + def handle_general_exception(e): + db.session.rollback() + traceback.print_exc() + + if request.path.startswith("/api"): + return ResponseHandler.error( + message=ErrorMessage.INTERNAL_ERROR, + status_code=HTTPStatus.INTERNAL_SERVER_ERROR + ) + + return render_template("errors/500.html"), 500 \ No newline at end of file diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index 90970e1..2190316 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -16,7 +16,11 @@ from app.models.laying_model import Laying dashboard_bp = Blueprint("dashboard", __name__, url_prefix="/dashboard") - +@dashboard_bp.route("/") +def dashboard(): + if not session.get("user_id"): + return redirect(url_for("auth.login")) + return render_template("dashboard.html", title="Business Intelligence Dashboard") @@ -144,11 +148,6 @@ def live_stats(): except Exception as e: return jsonify({"error": str(e)}), 500 -@dashboard_bp.route("/") -def dashboard(): - if not session.get("user_id"): - return redirect(url_for("auth.login")) - return render_template("dashboard.html", title="Business Intelligence Dashboard") diff --git a/app/routes/subcontractor_routes.py b/app/routes/subcontractor_routes.py index 5a6a4b6..0837659 100644 --- a/app/routes/subcontractor_routes.py +++ b/app/routes/subcontractor_routes.py @@ -1,34 +1,38 @@ -from flask import Blueprint, render_template, request, redirect, flash -from app import db +from flask import Blueprint, render_template, request, redirect, flash, current_app, url_for +from app.services.db_service import db from app.models.subcontractor_model import Subcontractor from app.utils.helpers import login_required - + subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor") - + + # ---------------- ADD ----------------- @subcontractor_bp.route("/add") @login_required def add_subcontractor(): + current_app.logger.info("Opened Add Subcontractor Page") return render_template("subcontractor/add.html") + + +# ---------------- SAVE ----------------- @subcontractor_bp.route("/save", methods=["POST"]) @login_required def save_subcontractor(): - # 1. Get and clean the name from the form + name = request.form.get("subcontractor_name", "").strip() - - # 2. Basic validation: Ensure the name isn't empty + if not name: + current_app.logger.warning("Empty subcontractor name submitted") flash("Subcontractor name cannot be empty.", "danger") - return redirect("/subcontractor/add") - - # 3. Check if a subcontractor with this name already exists + return redirect(url_for("subcontractor.add_subcontractor")) + existing_sub = Subcontractor.query.filter_by(subcontractor_name=name).first() - + if existing_sub: + current_app.logger.warning(f"Duplicate subcontractor attempt: {name}") flash(f"Subcontractor with name '{name}' already exists!", "danger") - return redirect("/subcontractor/add") - - # 4. If no duplicate is found, proceed to save + return redirect(url_for("subcontractor.add_subcontractor")) + try: subcontractor = Subcontractor( subcontractor_name=name, @@ -37,155 +41,99 @@ def save_subcontractor(): email_id=request.form.get("email_id"), gst_no=request.form.get("gst_no") ) - + db.session.add(subcontractor) db.session.commit() + + current_app.logger.info(f"Subcontractor Created Successfully: {name}") flash("Subcontractor added successfully!", "success") - - except Exception as e: + + except Exception: db.session.rollback() - flash("An error occurred while saving. Please try again.", "danger") - - return redirect("/subcontractor/list") - + current_app.logger.exception("Error while saving subcontractor") + flash("An error occurred while saving.", "danger") + + return redirect(url_for("subcontractor.subcontractor_list")) + + # ---------------- LIST ----------------- @subcontractor_bp.route("/list") @login_required def subcontractor_list(): subcontractors = Subcontractor.query.all() + current_app.logger.info("Viewed Subcontractor List") return render_template("subcontractor/list.html", subcontractors=subcontractors) - + + # ---------------- EDIT ----------------- @subcontractor_bp.route("/edit/") @login_required def edit_subcontractor(id): subcontractor = Subcontractor.query.get_or_404(id) + current_app.logger.info(f"Editing Subcontractor ID: {id}") return render_template("subcontractor/edit.html", subcontractor=subcontractor) - + + # ---------------- UPDATE ----------------- @subcontractor_bp.route("/update/", methods=["POST"]) @login_required def update_subcontractor(id): + subcontractor = Subcontractor.query.get_or_404(id) - new_name = request.form.get("subcontractor_name") - - # Check if the new name is taken by someone ELSE (not this current ID) + new_name = request.form.get("subcontractor_name", "").strip() + duplicate = Subcontractor.query.filter( Subcontractor.subcontractor_name == new_name, Subcontractor.id != id ).first() - - if duplicate: - flash("Another subcontractor already uses this name.", "danger") - return redirect(f"/subcontractor/edit/{id}") - - subcontractor.subcontractor_name = new_name - - db.session.commit() - - flash("Subcontractor updated successfully!", "success") - return redirect("/subcontractor/list") - -# ---------------- DELETE ----------------- -@subcontractor_bp.route("/delete/") -@login_required -def delete_subcontractor(id): - subcontractor = Subcontractor.query.get_or_404(id) - - db.session.delete(subcontractor) - db.session.commit() - - flash("Subcontractor deleted successfully!", "success") - return redirect("/subcontractor/list") -from flask import Blueprint, render_template, request, redirect, flash -from app import db -from app.models.subcontractor_model import Subcontractor -from app.utils.helpers import login_required - -subcontractor_bp = Blueprint("subcontractor", __name__, url_prefix="/subcontractor") - -# ---------------- ADD ----------------- -@subcontractor_bp.route("/add") -@login_required -def add_subcontractor(): - return render_template("subcontractor/add.html") -@subcontractor_bp.route("/save", methods=["POST"]) -@login_required -def save_subcontractor(): - name = request.form.get("subcontractor_name", "").strip() - if not name: - flash("Subcontractor name cannot be empty.", "danger") - return redirect("/subcontractor/add") - existing_sub = Subcontractor.query.filter_by(subcontractor_name=name).first() - - if existing_sub: - flash(f"Subcontractor with name '{name}' already exists!", "danger") - return redirect("/subcontractor/add") + if duplicate: + current_app.logger.warning(f"Duplicate update attempt: {new_name}") + flash("Another subcontractor already uses this name.", "danger") + return redirect(url_for("subcontractor.edit_subcontractor", id=id)) + try: - subcontractor = Subcontractor( - subcontractor_name=name, - contact_person=request.form.get("contact_person"), - mobile_no=request.form.get("mobile_no"), - email_id=request.form.get("email_id"), - gst_no=request.form.get("gst_no") - ) - - db.session.add(subcontractor) + old_name = subcontractor.subcontractor_name + + subcontractor.subcontractor_name = new_name + subcontractor.contact_person = request.form.get("contact_person") + subcontractor.mobile_no = request.form.get("mobile_no") + subcontractor.email_id = request.form.get("email_id") + subcontractor.gst_no = request.form.get("gst_no") + db.session.commit() - flash("Subcontractor added successfully!", "success") - - except Exception as e: + + current_app.logger.info( + f"Subcontractor Updated: {old_name} → {new_name}" + ) + + flash("Subcontractor updated successfully!", "success") + + except Exception: db.session.rollback() - flash("An error occurred while saving. Please try again.", "danger") - - return redirect("/subcontractor/list") - -# ---------------- LIST ----------------- -@subcontractor_bp.route("/list") -@login_required -def subcontractor_list(): - subcontractors = Subcontractor.query.all() - return render_template("subcontractor/list.html", subcontractors=subcontractors) - -# ---------------- EDIT ----------------- -@subcontractor_bp.route("/edit/") -@login_required -def edit_subcontractor(id): - subcontractor = Subcontractor.query.get_or_404(id) - return render_template("subcontractor/edit.html", subcontractor=subcontractor) - -# ---------------- UPDATE ----------------- -@subcontractor_bp.route("/update/", methods=["POST"]) -@login_required -def update_subcontractor(id): - subcontractor = Subcontractor.query.get_or_404(id) - new_name = request.form.get("subcontractor_name") - - # Check if the new name is taken by someone ELSE (not this current ID) - duplicate = Subcontractor.query.filter( - Subcontractor.subcontractor_name == new_name, - Subcontractor.id != id - ).first() - - if duplicate: - flash("Another subcontractor already uses this name.", "danger") - return redirect(f"/subcontractor/edit/{id}") - - subcontractor.subcontractor_name = new_name - db.session.commit() - - flash("Subcontractor updated successfully!", "success") - return redirect("/subcontractor/list") - + current_app.logger.exception("Error updating subcontractor") + flash("Update failed!", "danger") + + return redirect(url_for("subcontractor.subcontractor_list")) + + # ---------------- DELETE ----------------- @subcontractor_bp.route("/delete/") @login_required def delete_subcontractor(id): subcontractor = Subcontractor.query.get_or_404(id) + try: + name = subcontractor.subcontractor_name - db.session.delete(subcontractor) - db.session.commit() + db.session.delete(subcontractor) + db.session.commit() - flash("Subcontractor deleted successfully!", "success") - return redirect("/subcontractor/list") \ No newline at end of file + current_app.logger.info(f"Subcontractor Deleted: {name}") + flash("Subcontractor deleted successfully!", "success") + + except Exception: + db.session.rollback() + current_app.logger.exception("Error deleting subcontractor") + flash("Delete failed!", "danger") + + return redirect(url_for("subcontractor.subcontractor_list")) \ No newline at end of file diff --git a/app/routes/user.py b/app/routes/user.py index 5f16e3c..afef697 100644 --- a/app/routes/user.py +++ b/app/routes/user.py @@ -1,11 +1,13 @@ from flask import Blueprint, render_template from app.services.user_service import UserService from app.utils.helpers import login_required +from flask import current_app user_bp = Blueprint("user", __name__, url_prefix="/user") @user_bp.route("/list") @login_required def list_users(): + current_app.logger.info("User list viewed") users = UserService.get_all_users() - return render_template("users.html", users=users, title="Users") + return render_template("users.html", users=users, title="Users") \ No newline at end of file diff --git a/app/services/comparison_service.py b/app/services/comparison_service.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/logger_service.py b/app/services/logger_service.py new file mode 100644 index 0000000..347f396 --- /dev/null +++ b/app/services/logger_service.py @@ -0,0 +1,82 @@ +import os +import logging +from logging.handlers import RotatingFileHandler +from flask import request, session + + +class RequestFormatter(logging.Formatter): + """ + Custom formatter to safely inject request data + """ + + def format(self, record): + record.user = getattr(record, "user", "Anonymous") + record.ip = getattr(record, "ip", "N/A") + record.method = getattr(record, "method", "N/A") + record.url = getattr(record, "url", "N/A") + return super().format(record) + + +class LoggerService: + + @staticmethod + def init_app(app): + + # Create logs folder if not exists + if not os.path.exists("logs"): + os.makedirs("logs") + + formatter = RequestFormatter( + "%(asctime)s | %(levelname)s | " + "User:%(user)s | IP:%(ip)s | " + "Method:%(method)s | URL:%(url)s | " + "%(message)s" + ) + + # 🔹 INFO LOG + info_handler = RotatingFileHandler( + "logs/app.log", + maxBytes=5 * 1024 * 1024, + backupCount=5 + ) + info_handler.setLevel(logging.INFO) + info_handler.setFormatter(formatter) + + # 🔹 ERROR LOG + error_handler = RotatingFileHandler( + "logs/error.log", + maxBytes=5 * 1024 * 1024, + backupCount=5 + ) + error_handler.setLevel(logging.ERROR) + error_handler.setFormatter(formatter) + + # 🔹 CONSOLE LOG + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.DEBUG) + console_handler.setFormatter(formatter) + + app.logger.setLevel(logging.DEBUG) + app.logger.addHandler(info_handler) + app.logger.addHandler(error_handler) + app.logger.addHandler(console_handler) + + # Auto request logging + @app.before_request + def log_request(): + app.logger.info( + "Request Started", + extra=LoggerService.get_request_data() + ) + + @staticmethod + def get_request_data(): + try: + return { + "user": session.get("user_email", "Anonymous"), + "ip": request.remote_addr, + "method": request.method, + "url": request.url + } + except: + return {} \ No newline at end of file diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index a5e5716..4399c4b 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -2,7 +2,7 @@ {% block content %}
-

Comparison dSoftware Solapur (UGD) - Live Dashboard

+

Comparison Software Solapur (UGD) - Live Dashboard

diff --git a/app/templates/subcontractor/add.html b/app/templates/subcontractor/add.html index ef73ac1..b059196 100644 --- a/app/templates/subcontractor/add.html +++ b/app/templates/subcontractor/add.html @@ -2,39 +2,39 @@ {% block content %}
-

Add New Subcontractor

-
+
- +
- +
- +
- +
- +
-
+ Back +
{% endblock %} \ No newline at end of file diff --git a/app/templates/subcontractor/edit.html b/app/templates/subcontractor/edit.html index 4c4c037..d0f9ed2 100644 --- a/app/templates/subcontractor/edit.html +++ b/app/templates/subcontractor/edit.html @@ -4,7 +4,7 @@

Edit Subcontractor

-
+
@@ -33,7 +33,7 @@
- Back + Back
diff --git a/app/templates/subcontractor/list.html b/app/templates/subcontractor/list.html index 2acd851..909785a 100644 --- a/app/templates/subcontractor/list.html +++ b/app/templates/subcontractor/list.html @@ -12,8 +12,9 @@
- - ➕ Add Subcontractor + + + ➕ Add Subcontractor
@@ -56,11 +57,15 @@ diff --git a/app/templates/subcontractor_dashboard.html b/app/templates/subcontractor_dashboard.html index f1aa700..e9e1d24 100644 --- a/app/templates/subcontractor_dashboard.html +++ b/app/templates/subcontractor_dashboard.html @@ -6,24 +6,117 @@

Subcontractor Dashboard

- -
+
+
+
+
+
Trenching Units
+

0

+
+
+
+
+
+
+
Manhole Units
+

0

+
+
+
+
+
+
+
Laying Units
+

0

+
+
+
+
- +
-
- Work Category Bar Chart -
-
- +
Live Category Bar Chart
+
+
- +
+
+
Location Distribution Pie Chart
+
+ +
+
+
+ + + + + {% endblock %} \ No newline at end of file diff --git a/app/utils/exceptions.py b/app/utils/exceptions.py new file mode 100644 index 0000000..b9571f6 --- /dev/null +++ b/app/utils/exceptions.py @@ -0,0 +1,8 @@ +from app.constants.http_status import HTTPStatus + +class APIException(Exception): + def __init__(self, message, status_code=HTTPStatus.BAD_REQUEST, errors=None): + self.message = message + self.status_code = status_code + self.errors = errors + super().__init__(self.message) \ No newline at end of file diff --git a/app/utils/response_handler.py b/app/utils/response_handler.py new file mode 100644 index 0000000..3154104 --- /dev/null +++ b/app/utils/response_handler.py @@ -0,0 +1,23 @@ +from flask import jsonify +from app.constants.http_status import HTTPStatus + + +class ResponseHandler: + + @staticmethod + def success(message, data=None, status_code=HTTPStatus.OK): + return jsonify({ + "status": "success", + "message": message, + "data": data if data else {}, + "errors": [] + }), status_code + + @staticmethod + def error(message, errors=None, status_code=HTTPStatus.BAD_REQUEST): + return jsonify({ + "status": "error", + "message": message, + "data": {}, + "errors": errors if errors else [] + }), status_code \ No newline at end of file