feat: Set up SessionMiddleware and Jinja2 Template Configuration
- Add SECRET_KEY environment variable and .env file for session management - Configure SessionMiddleware with FastAPI for user session handling - Set up Jinja2 template engine with template directory configuration - Mount static files directory for CSS, JS, and image assets - Create comprehensive base.html template with Bootstrap 5 CDN - Add Bootstrap Icons and custom styling support - Include responsive navigation with user authentication state - Create placeholder CSS and JavaScript files for customization - Add aiofiles dependency for static file serving This establishes the web framework foundation with session management and templating system ready for frontend development.
This commit is contained in:
3
.env
Normal file
3
.env
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Delphi Database Environment Configuration
|
||||||
|
SECRET_KEY=your-secret-key-here-change-this-in-production
|
||||||
|
DATABASE_URL=sqlite:///./delphi.db
|
||||||
33
app/main.py
33
app/main.py
@@ -5,20 +5,36 @@ This module initializes the FastAPI application, sets up database connections,
|
|||||||
and provides the main application instance.
|
and provides the main application instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import logging
|
import logging
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from fastapi import FastAPI, Depends
|
from fastapi import FastAPI, Depends, Request
|
||||||
|
from fastapi.middleware.sessions import SessionMiddleware
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from .database import create_tables, get_db, get_database_url
|
from .database import create_tables, get_db, get_database_url
|
||||||
from .models import User
|
from .models import User
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# Get SECRET_KEY from environment variables
|
||||||
|
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||||
|
if not SECRET_KEY:
|
||||||
|
raise ValueError("SECRET_KEY environment variable must be set")
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Configure Jinja2 templates
|
||||||
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
@@ -54,6 +70,21 @@ app = FastAPI(
|
|||||||
lifespan=lifespan
|
lifespan=lifespan
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add CORS middleware for cross-origin requests
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"], # In production, specify allowed origins
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add SessionMiddleware for session management
|
||||||
|
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
|
||||||
|
|
||||||
|
# Mount static files directory
|
||||||
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
|
|||||||
@@ -1 +1,87 @@
|
|||||||
<!-- Base template with Bootstrap 5 and navigation -->
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{% block title %}Delphi Database{% endblock %}</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap 5 CSS CDN -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Bootstrap Icons -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<!-- Custom CSS -->
|
||||||
|
<link href="{{ url_for('static', path='/css/custom.css') }}" rel="stylesheet">
|
||||||
|
|
||||||
|
{% block extra_head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="/">
|
||||||
|
<img src="{{ url_for('static', path='/logo/delphi-logo.webp') }}" alt="Delphi Logo" width="30" height="30" class="d-inline-block align-text-top me-2">
|
||||||
|
Delphi Database
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
|
<ul class="navbar-nav me-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if request.url.path == '/' %}active{% endif %}" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if 'dashboard' in request.url.path %}active{% endif %}" href="/dashboard">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if 'admin' in request.url.path %}active{% endif %}" href="/admin">Admin</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{% if 'user' in session %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-person-circle me-1"></i>{{ session.user.username }}
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="userDropdown">
|
||||||
|
<li><a class="dropdown-item" href="/profile">Profile</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><a class="dropdown-item" href="/logout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link {% if 'login' in request.url.path %}active{% endif %}" href="/login">Login</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="container-fluid mt-4">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="bg-light text-center text-muted mt-5 py-3">
|
||||||
|
<div class="container">
|
||||||
|
<small>© 2025 Delphi Database. All rights reserved.</small>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- Bootstrap 5 JS Bundle CDN -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
|
<!-- Custom JS -->
|
||||||
|
<script src="{{ url_for('static', path='/js/custom.js') }}"></script>
|
||||||
|
|
||||||
|
{% block extra_scripts %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ passlib[bcrypt]==1.7.4
|
|||||||
python-dotenv==1.0.0
|
python-dotenv==1.0.0
|
||||||
uvicorn[standard]==0.24.0
|
uvicorn[standard]==0.24.0
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
|
aiofiles==23.2.1
|
||||||
|
|||||||
51
static/css/custom.css
Normal file
51
static/css/custom.css
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* Custom CSS for Delphi Database */
|
||||||
|
|
||||||
|
/* Additional styles can be added here */
|
||||||
|
body {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom navbar styles */
|
||||||
|
.navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom card styles */
|
||||||
|
.card {
|
||||||
|
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.125);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom button styles */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0b5ed7;
|
||||||
|
border-color: #0a58ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom form styles */
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: #86b7fe;
|
||||||
|
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom table styles */
|
||||||
|
.table th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom alert styles */
|
||||||
|
.alert {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom footer styles */
|
||||||
|
footer {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
82
static/js/custom.js
Normal file
82
static/js/custom.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// Custom JavaScript for Delphi Database
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize tooltips if any
|
||||||
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto-hide alerts after 5 seconds
|
||||||
|
var alerts = document.querySelectorAll('.alert:not(.alert-permanent)');
|
||||||
|
alerts.forEach(function(alert) {
|
||||||
|
setTimeout(function() {
|
||||||
|
var bsAlert = new bootstrap.Alert(alert);
|
||||||
|
bsAlert.close();
|
||||||
|
}, 5000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confirm delete actions
|
||||||
|
var deleteButtons = document.querySelectorAll('[data-confirm-delete]');
|
||||||
|
deleteButtons.forEach(function(button) {
|
||||||
|
button.addEventListener('click', function(e) {
|
||||||
|
var message = this.getAttribute('data-confirm-delete') || 'Are you sure you want to delete this item?';
|
||||||
|
if (!confirm(message)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form validation enhancement
|
||||||
|
var forms = document.querySelectorAll('.needs-validation');
|
||||||
|
Array.prototype.slice.call(forms).forEach(function(form) {
|
||||||
|
form.addEventListener('submit', function(event) {
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
form.classList.add('was-validated');
|
||||||
|
}, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Dynamic form field enabling/disabling
|
||||||
|
var toggleFields = document.querySelectorAll('[data-toggle-field]');
|
||||||
|
toggleFields.forEach(function(element) {
|
||||||
|
element.addEventListener('change', function() {
|
||||||
|
var targetSelector = this.getAttribute('data-toggle-field');
|
||||||
|
var targetField = document.querySelector(targetSelector);
|
||||||
|
if (targetField) {
|
||||||
|
targetField.disabled = !this.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
function formatDate(dateString) {
|
||||||
|
if (!dateString) return '';
|
||||||
|
var date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCurrency(amount) {
|
||||||
|
if (!amount) return '$0.00';
|
||||||
|
return new Intl.NumberFormat('en-US', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'USD'
|
||||||
|
}).format(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLoading(button) {
|
||||||
|
if (button) {
|
||||||
|
button.disabled = true;
|
||||||
|
button.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Loading...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading(button, originalText) {
|
||||||
|
if (button) {
|
||||||
|
button.disabled = false;
|
||||||
|
button.innerHTML = originalText;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user