Spaces:
Paused
Paused
Upload 20 files
Browse files- Dockerfile +25 -0
- __pycache__/auth.cpython-312.pyc +0 -0
- __pycache__/database.cpython-312.pyc +0 -0
- __pycache__/main.cpython-312.pyc +0 -0
- __pycache__/models.cpython-312.pyc +0 -0
- app.db +0 -0
- auth.py +27 -0
- database.py +10 -0
- main.py +220 -0
- models.py +29 -0
- requirements.txt +7 -0
- static/.keep +1 -0
- static/custom.css +136 -0
- templates/admin_dashboard.html +41 -0
- templates/admin_edit.html +94 -0
- templates/admin_view.html +26 -0
- templates/base.html +78 -0
- templates/login.html +38 -0
- templates/signup.html +68 -0
- templates/user_form.html +93 -0
Dockerfile
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use official Python image
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# Set environment variables
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 6 |
+
ENV PYTHONUNBUFFERED=1
|
| 7 |
+
|
| 8 |
+
# Set work directory
|
| 9 |
+
WORKDIR /app
|
| 10 |
+
|
| 11 |
+
# Install system dependencies
|
| 12 |
+
RUN apt-get update && apt-get install -y build-essential libffi-dev && rm -rf /var/lib/apt/lists/*
|
| 13 |
+
|
| 14 |
+
# Install Python dependencies
|
| 15 |
+
COPY requirements.txt .
|
| 16 |
+
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 17 |
+
|
| 18 |
+
# Copy project files
|
| 19 |
+
COPY . .
|
| 20 |
+
|
| 21 |
+
# Expose the port Hugging Face expects
|
| 22 |
+
EXPOSE 7860
|
| 23 |
+
|
| 24 |
+
# Start the FastAPI server on port 7860
|
| 25 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--reload"]
|
__pycache__/auth.cpython-312.pyc
ADDED
|
Binary file (1.7 kB). View file
|
|
|
__pycache__/database.cpython-312.pyc
ADDED
|
Binary file (557 Bytes). View file
|
|
|
__pycache__/main.cpython-312.pyc
ADDED
|
Binary file (15.6 kB). View file
|
|
|
__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (1.78 kB). View file
|
|
|
app.db
ADDED
|
Binary file (24.6 kB). View file
|
|
|
auth.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from passlib.context import CryptContext
|
| 2 |
+
from sqlalchemy.orm import Session
|
| 3 |
+
from models import User
|
| 4 |
+
|
| 5 |
+
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
| 6 |
+
|
| 7 |
+
def verify_password(plain_password, hashed_password):
|
| 8 |
+
return pwd_context.verify(plain_password, hashed_password)
|
| 9 |
+
|
| 10 |
+
def get_password_hash(password):
|
| 11 |
+
return pwd_context.hash(password)
|
| 12 |
+
|
| 13 |
+
def authenticate_user(db: Session, username: str, password: str):
|
| 14 |
+
user = db.query(User).filter(User.username == username).first()
|
| 15 |
+
if not user:
|
| 16 |
+
return None
|
| 17 |
+
if not verify_password(password, user.password):
|
| 18 |
+
return None
|
| 19 |
+
return user
|
| 20 |
+
|
| 21 |
+
def create_user(db: Session, username: str, password: str, is_admin: bool = False):
|
| 22 |
+
hashed_password = get_password_hash(password)
|
| 23 |
+
db_user = User(username=username, password=hashed_password, is_admin=is_admin)
|
| 24 |
+
db.add(db_user)
|
| 25 |
+
db.commit()
|
| 26 |
+
db.refresh(db_user)
|
| 27 |
+
return db_user
|
database.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import create_engine
|
| 2 |
+
from sqlalchemy.ext.declarative import declarative_base
|
| 3 |
+
from sqlalchemy.orm import sessionmaker
|
| 4 |
+
|
| 5 |
+
SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db"
|
| 6 |
+
engine = create_engine(
|
| 7 |
+
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
|
| 8 |
+
)
|
| 9 |
+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
| 10 |
+
Base = declarative_base()
|
main.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, Request, Form, Depends, status
|
| 2 |
+
from fastapi.responses import RedirectResponse, HTMLResponse
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
+
from fastapi.templating import Jinja2Templates
|
| 5 |
+
from starlette.middleware.sessions import SessionMiddleware
|
| 6 |
+
from sqlalchemy.orm import Session
|
| 7 |
+
from database import engine, Base, SessionLocal
|
| 8 |
+
from models import User, UserDetails
|
| 9 |
+
from auth import authenticate_user, create_user, get_password_hash
|
| 10 |
+
import uvicorn
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
|
| 13 |
+
app = FastAPI()
|
| 14 |
+
app.add_middleware(SessionMiddleware, secret_key="your-secret-key")
|
| 15 |
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 16 |
+
templates = Jinja2Templates(directory="templates")
|
| 17 |
+
Base.metadata.create_all(bind=engine)
|
| 18 |
+
|
| 19 |
+
def get_db():
|
| 20 |
+
db = SessionLocal()
|
| 21 |
+
try:
|
| 22 |
+
yield db
|
| 23 |
+
finally:
|
| 24 |
+
db.close()
|
| 25 |
+
|
| 26 |
+
@app.get("/", response_class=HTMLResponse)
|
| 27 |
+
def index(request: Request):
|
| 28 |
+
if request.session.get("user_id"):
|
| 29 |
+
return RedirectResponse("/user/form", status_code=302)
|
| 30 |
+
return templates.TemplateResponse("login.html", {"request": request, "success": request.session.pop("success", None), "error": request.session.pop("error", None)})
|
| 31 |
+
|
| 32 |
+
@app.get("/login", response_class=HTMLResponse)
|
| 33 |
+
def login_get(request: Request):
|
| 34 |
+
return templates.TemplateResponse("login.html", {"request": request, "success": request.session.pop("success", None), "error": request.session.pop("error", None)})
|
| 35 |
+
|
| 36 |
+
@app.post("/login", response_class=HTMLResponse)
|
| 37 |
+
def login_post(request: Request, username: str = Form(...), password: str = Form(...), db: Session = Depends(get_db)):
|
| 38 |
+
user = authenticate_user(db, username, password)
|
| 39 |
+
if user:
|
| 40 |
+
request.session["user_id"] = user.id
|
| 41 |
+
request.session["is_admin"] = user.is_admin
|
| 42 |
+
request.session["success"] = "Login successful!"
|
| 43 |
+
if user.is_admin:
|
| 44 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 45 |
+
else:
|
| 46 |
+
return RedirectResponse("/user/form", status_code=302)
|
| 47 |
+
request.session["error"] = "Invalid credentials"
|
| 48 |
+
return RedirectResponse("/login", status_code=302)
|
| 49 |
+
|
| 50 |
+
@app.get("/signup", response_class=HTMLResponse)
|
| 51 |
+
def signup_get(request: Request):
|
| 52 |
+
return templates.TemplateResponse("signup.html", {"request": request, "success": request.session.pop("success", None), "error": request.session.pop("error", None)})
|
| 53 |
+
|
| 54 |
+
@app.post("/signup", response_class=HTMLResponse)
|
| 55 |
+
def signup_post(request: Request, username: str = Form(...), password: str = Form(...), db: Session = Depends(get_db)):
|
| 56 |
+
if db.query(User).filter(User.username == username).first():
|
| 57 |
+
request.session["error"] = "Username already exists"
|
| 58 |
+
return RedirectResponse("/signup", status_code=302)
|
| 59 |
+
create_user(db, username, password)
|
| 60 |
+
request.session["success"] = "Signup successful! Please login."
|
| 61 |
+
return RedirectResponse("/login", status_code=302)
|
| 62 |
+
|
| 63 |
+
@app.get("/logout")
|
| 64 |
+
def logout(request: Request):
|
| 65 |
+
request.session.clear()
|
| 66 |
+
return RedirectResponse("/login", status_code=302)
|
| 67 |
+
|
| 68 |
+
@app.get("/user/form", response_class=HTMLResponse)
|
| 69 |
+
def user_form_get(request: Request, db: Session = Depends(get_db)):
|
| 70 |
+
user_id = request.session.get("user_id")
|
| 71 |
+
if not user_id:
|
| 72 |
+
return RedirectResponse("/login", status_code=302)
|
| 73 |
+
user = db.query(User).filter(User.id == user_id).first()
|
| 74 |
+
details = user.details if user and user.details else None
|
| 75 |
+
return templates.TemplateResponse("user_form.html", {"request": request, "details": details or {}, "success": request.session.pop("success", None), "error": request.session.pop("error", None)})
|
| 76 |
+
|
| 77 |
+
@app.post("/user/form", response_class=HTMLResponse)
|
| 78 |
+
def user_form_post(request: Request,
|
| 79 |
+
first_name: str = Form(...),
|
| 80 |
+
last_name: str = Form(...),
|
| 81 |
+
email: str = Form(...),
|
| 82 |
+
mobile: str = Form(...),
|
| 83 |
+
dob: str = Form(...),
|
| 84 |
+
gender: str = Form(...),
|
| 85 |
+
current_semester: str = Form(...),
|
| 86 |
+
tenth_percentage: float = Form(...),
|
| 87 |
+
twelfth_percentage: float = Form(...),
|
| 88 |
+
graduation_percentage: float = Form(...),
|
| 89 |
+
specialization: str = Form(...),
|
| 90 |
+
experience_status: str = Form(...),
|
| 91 |
+
db: Session = Depends(get_db)):
|
| 92 |
+
user_id = request.session.get("user_id")
|
| 93 |
+
if not user_id:
|
| 94 |
+
return RedirectResponse("/login", status_code=302)
|
| 95 |
+
user = db.query(User).filter(User.id == user_id).first()
|
| 96 |
+
if not user:
|
| 97 |
+
return RedirectResponse("/login", status_code=302)
|
| 98 |
+
details = user.details
|
| 99 |
+
if not details:
|
| 100 |
+
details = UserDetails(user_id=user.id)
|
| 101 |
+
db.add(details)
|
| 102 |
+
msg = "Details created!"
|
| 103 |
+
else:
|
| 104 |
+
msg = "Details updated!"
|
| 105 |
+
details.first_name = first_name
|
| 106 |
+
details.last_name = last_name
|
| 107 |
+
details.email = email
|
| 108 |
+
details.mobile = mobile
|
| 109 |
+
details.dob = datetime.strptime(dob, "%Y-%m-%d").date()
|
| 110 |
+
details.gender = gender
|
| 111 |
+
details.current_semester = current_semester
|
| 112 |
+
details.tenth_percentage = tenth_percentage
|
| 113 |
+
details.twelfth_percentage = twelfth_percentage
|
| 114 |
+
details.graduation_percentage = graduation_percentage
|
| 115 |
+
details.specialization = specialization
|
| 116 |
+
details.experience_status = experience_status
|
| 117 |
+
db.commit()
|
| 118 |
+
request.session["success"] = msg
|
| 119 |
+
return RedirectResponse("/user/form", status_code=302)
|
| 120 |
+
|
| 121 |
+
@app.get("/admin/dashboard", response_class=HTMLResponse)
|
| 122 |
+
def admin_dashboard(request: Request, search: str = "", db: Session = Depends(get_db)):
|
| 123 |
+
if not request.session.get("is_admin"):
|
| 124 |
+
return RedirectResponse("/login", status_code=302)
|
| 125 |
+
query = db.query(User).filter(User.is_admin == False)
|
| 126 |
+
if search:
|
| 127 |
+
query = query.join(UserDetails).filter(
|
| 128 |
+
(User.username.contains(search)) |
|
| 129 |
+
(UserDetails.first_name.contains(search)) |
|
| 130 |
+
(UserDetails.last_name.contains(search)) |
|
| 131 |
+
(UserDetails.email.contains(search))
|
| 132 |
+
)
|
| 133 |
+
users = query.all()
|
| 134 |
+
return templates.TemplateResponse("admin_dashboard.html", {"request": request, "users": users, "search": search, "success": request.session.pop("success", None), "error": request.session.pop("error", None)})
|
| 135 |
+
|
| 136 |
+
@app.get("/admin/user/{user_id}", response_class=HTMLResponse)
|
| 137 |
+
def admin_view_user(request: Request, user_id: int, db: Session = Depends(get_db)):
|
| 138 |
+
if not request.session.get("is_admin"):
|
| 139 |
+
return RedirectResponse("/login", status_code=302)
|
| 140 |
+
user = db.query(User).filter(User.id == user_id).first()
|
| 141 |
+
if not user:
|
| 142 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 143 |
+
details = user.details
|
| 144 |
+
return templates.TemplateResponse("admin_view.html", {"request": request, "user": user, "details": details})
|
| 145 |
+
|
| 146 |
+
@app.get("/admin/user/{user_id}/edit", response_class=HTMLResponse)
|
| 147 |
+
def admin_edit_user_get(request: Request, user_id: int, db: Session = Depends(get_db)):
|
| 148 |
+
if not request.session.get("is_admin"):
|
| 149 |
+
return RedirectResponse("/login", status_code=302)
|
| 150 |
+
user = db.query(User).filter(User.id == user_id).first()
|
| 151 |
+
if not user:
|
| 152 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 153 |
+
details = user.details or None
|
| 154 |
+
return templates.TemplateResponse("admin_edit.html", {"request": request, "user": user, "details": details or {}, "success": request.session.pop("success", None), "error": request.session.pop("error", None)})
|
| 155 |
+
|
| 156 |
+
@app.post("/admin/user/{user_id}/edit", response_class=HTMLResponse)
|
| 157 |
+
def admin_edit_user_post(request: Request, user_id: int,
|
| 158 |
+
first_name: str = Form(...),
|
| 159 |
+
last_name: str = Form(...),
|
| 160 |
+
email: str = Form(...),
|
| 161 |
+
mobile: str = Form(...),
|
| 162 |
+
dob: str = Form(...),
|
| 163 |
+
gender: str = Form(...),
|
| 164 |
+
current_semester: str = Form(...),
|
| 165 |
+
tenth_percentage: float = Form(...),
|
| 166 |
+
twelfth_percentage: float = Form(...),
|
| 167 |
+
graduation_percentage: float = Form(...),
|
| 168 |
+
specialization: str = Form(...),
|
| 169 |
+
experience_status: str = Form(...),
|
| 170 |
+
db: Session = Depends(get_db)):
|
| 171 |
+
if not request.session.get("is_admin"):
|
| 172 |
+
return RedirectResponse("/login", status_code=302)
|
| 173 |
+
user = db.query(User).filter(User.id == user_id).first()
|
| 174 |
+
if not user:
|
| 175 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 176 |
+
details = user.details
|
| 177 |
+
if not details:
|
| 178 |
+
details = UserDetails(user_id=user.id)
|
| 179 |
+
db.add(details)
|
| 180 |
+
msg = "User details created!"
|
| 181 |
+
else:
|
| 182 |
+
msg = "User details updated!"
|
| 183 |
+
details.first_name = first_name
|
| 184 |
+
details.last_name = last_name
|
| 185 |
+
details.email = email
|
| 186 |
+
details.mobile = mobile
|
| 187 |
+
details.dob = datetime.strptime(dob, "%Y-%m-%d").date()
|
| 188 |
+
details.gender = gender
|
| 189 |
+
details.current_semester = current_semester
|
| 190 |
+
details.tenth_percentage = tenth_percentage
|
| 191 |
+
details.twelfth_percentage = twelfth_percentage
|
| 192 |
+
details.graduation_percentage = graduation_percentage
|
| 193 |
+
details.specialization = specialization
|
| 194 |
+
details.experience_status = experience_status
|
| 195 |
+
db.commit()
|
| 196 |
+
request.session["success"] = msg
|
| 197 |
+
return RedirectResponse(f"/admin/user/{user_id}/edit", status_code=302)
|
| 198 |
+
|
| 199 |
+
@app.get("/admin/user/{user_id}/delete")
|
| 200 |
+
def admin_delete_user(request: Request, user_id: int, db: Session = Depends(get_db)):
|
| 201 |
+
if not request.session.get("is_admin"):
|
| 202 |
+
return RedirectResponse("/login", status_code=302)
|
| 203 |
+
user = db.query(User).filter(User.id == user_id).first()
|
| 204 |
+
if user:
|
| 205 |
+
db.delete(user)
|
| 206 |
+
db.commit()
|
| 207 |
+
request.session["success"] = "User deleted!"
|
| 208 |
+
return RedirectResponse("/admin/dashboard", status_code=302)
|
| 209 |
+
|
| 210 |
+
@app.get("/create-admin")
|
| 211 |
+
def create_admin(request: Request, db: Session = Depends(get_db)):
|
| 212 |
+
if db.query(User).filter(User.username == "admin@149gmail.com").first():
|
| 213 |
+
request.session["error"] = "Admin already exists."
|
| 214 |
+
return RedirectResponse("/login", status_code=302)
|
| 215 |
+
create_user(db, "admin@149gmail.com", "Admin@149", is_admin=True)
|
| 216 |
+
request.session["success"] = "Admin created. You can now login as admin."
|
| 217 |
+
return RedirectResponse("/login", status_code=302)
|
| 218 |
+
|
| 219 |
+
if __name__ == "__main__":
|
| 220 |
+
uvicorn.run("main:app", host="127.0.0.1", port=7860, reload=True)
|
models.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sqlalchemy import Column, Integer, String, Boolean, Date, Float, ForeignKey
|
| 2 |
+
from sqlalchemy.orm import relationship
|
| 3 |
+
from database import Base
|
| 4 |
+
|
| 5 |
+
class User(Base):
|
| 6 |
+
__tablename__ = "users"
|
| 7 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 8 |
+
username = Column(String, unique=True, index=True, nullable=False)
|
| 9 |
+
password = Column(String, nullable=False)
|
| 10 |
+
is_admin = Column(Boolean, default=False)
|
| 11 |
+
details = relationship("UserDetails", back_populates="user", uselist=False)
|
| 12 |
+
|
| 13 |
+
class UserDetails(Base):
|
| 14 |
+
__tablename__ = "user_details"
|
| 15 |
+
id = Column(Integer, primary_key=True, index=True)
|
| 16 |
+
user_id = Column(Integer, ForeignKey("users.id"))
|
| 17 |
+
first_name = Column(String)
|
| 18 |
+
last_name = Column(String)
|
| 19 |
+
email = Column(String)
|
| 20 |
+
mobile = Column(String)
|
| 21 |
+
dob = Column(Date)
|
| 22 |
+
gender = Column(String)
|
| 23 |
+
current_semester = Column(String)
|
| 24 |
+
tenth_percentage = Column(Float)
|
| 25 |
+
twelfth_percentage = Column(Float)
|
| 26 |
+
graduation_percentage = Column(Float)
|
| 27 |
+
specialization = Column(String)
|
| 28 |
+
experience_status = Column(String)
|
| 29 |
+
user = relationship("User", back_populates="details")
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
jinja2
|
| 4 |
+
sqlalchemy
|
| 5 |
+
passlib[bcrypt]
|
| 6 |
+
python-multipart
|
| 7 |
+
itsdangerous
|
static/.keep
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
|
static/custom.css
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
min-height: 100vh;
|
| 3 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 4 |
+
position: relative;
|
| 5 |
+
overflow-x: hidden;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.blob-bg {
|
| 9 |
+
position: fixed;
|
| 10 |
+
z-index: 0;
|
| 11 |
+
top: -100px;
|
| 12 |
+
left: -100px;
|
| 13 |
+
width: 400px;
|
| 14 |
+
height: 400px;
|
| 15 |
+
background: radial-gradient(circle at 60% 40%, #a18cd1 0%, #fbc2eb 100%);
|
| 16 |
+
opacity: 0.5;
|
| 17 |
+
filter: blur(80px);
|
| 18 |
+
border-radius: 50%;
|
| 19 |
+
animation: blobMove 15s infinite alternate ease-in-out;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.blob-bg2 {
|
| 23 |
+
position: fixed;
|
| 24 |
+
z-index: 0;
|
| 25 |
+
bottom: -120px;
|
| 26 |
+
right: -120px;
|
| 27 |
+
width: 420px;
|
| 28 |
+
height: 420px;
|
| 29 |
+
background: radial-gradient(circle at 40% 60%, #fad0c4 0%, #ffd1ff 100%);
|
| 30 |
+
opacity: 0.4;
|
| 31 |
+
filter: blur(90px);
|
| 32 |
+
border-radius: 50%;
|
| 33 |
+
animation: blobMove2 18s infinite alternate ease-in-out;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
@keyframes blobMove {
|
| 37 |
+
0% { transform: scale(1) translateY(0) translateX(0); }
|
| 38 |
+
100% { transform: scale(1.2) translateY(40px) translateX(60px); }
|
| 39 |
+
}
|
| 40 |
+
@keyframes blobMove2 {
|
| 41 |
+
0% { transform: scale(1) translateY(0) translateX(0); }
|
| 42 |
+
100% { transform: scale(1.1) translateY(-30px) translateX(-50px); }
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.card, .table, .form-control, .form-select, .btn {
|
| 46 |
+
border-radius: 1.5rem !important;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.card {
|
| 50 |
+
box-shadow: 0 4px 32px 0 rgba(80, 80, 160, 0.09);
|
| 51 |
+
border: none;
|
| 52 |
+
background: rgba(255,255,255,0.95);
|
| 53 |
+
backdrop-filter: blur(2px);
|
| 54 |
+
transition: box-shadow 0.2s;
|
| 55 |
+
}
|
| 56 |
+
.card:hover {
|
| 57 |
+
box-shadow: 0 8px 40px 0 rgba(80, 80, 160, 0.18);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.admin-card {
|
| 61 |
+
background: rgba(245, 245, 255, 0.97);
|
| 62 |
+
border: 2px solid #d1d8fc;
|
| 63 |
+
box-shadow: 0 4px 28px 0 rgba(81, 86, 255, 0.09);
|
| 64 |
+
border-radius: 1.8rem !important;
|
| 65 |
+
margin-bottom: 1.5rem;
|
| 66 |
+
overflow: hidden;
|
| 67 |
+
}
|
| 68 |
+
.admin-card .card-header {
|
| 69 |
+
font-weight: 600;
|
| 70 |
+
letter-spacing: 0.5px;
|
| 71 |
+
background: linear-gradient(90deg, #5f72bd 0%, #9b23ea 100%) !important;
|
| 72 |
+
color: #fff !important;
|
| 73 |
+
border-radius: 1.8rem 1.8rem 0 0 !important;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
.admin-navbar {
|
| 77 |
+
background: linear-gradient(90deg, #5f72bd 0%, #9b23ea 100%) !important;
|
| 78 |
+
box-shadow: 0 4px 24px 0 rgba(81, 86, 255, 0.09);
|
| 79 |
+
border-radius: 0 0 1.5rem 1.5rem;
|
| 80 |
+
margin-top: 0;
|
| 81 |
+
margin-bottom: 1.2rem;
|
| 82 |
+
padding-left: 1.2rem;
|
| 83 |
+
padding-right: 1.2rem;
|
| 84 |
+
backdrop-filter: blur(2px);
|
| 85 |
+
position: sticky;
|
| 86 |
+
top: 0;
|
| 87 |
+
z-index: 1050;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.btn-primary, .btn-success, .btn-info, .btn-warning, .btn-danger, .btn-secondary {
|
| 91 |
+
border-radius: 2rem;
|
| 92 |
+
font-weight: 600;
|
| 93 |
+
letter-spacing: 0.5px;
|
| 94 |
+
box-shadow: 0 2px 8px 0 rgba(80, 80, 160, 0.08);
|
| 95 |
+
transition: background 0.2s, box-shadow 0.2s;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.btn-primary:hover, .btn-success:hover, .btn-info:hover, .btn-warning:hover, .btn-danger:hover, .btn-secondary:hover {
|
| 99 |
+
filter: brightness(0.95);
|
| 100 |
+
box-shadow: 0 4px 16px 0 rgba(80, 80, 160, 0.12);
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.form-control:focus, .form-select:focus {
|
| 104 |
+
border-color: #a18cd1;
|
| 105 |
+
box-shadow: 0 0 0 0.2rem rgba(161,140,209,.15);
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
.alert {
|
| 109 |
+
border-radius: 1.5rem;
|
| 110 |
+
font-size: 1rem;
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
.navbar {
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
@media (max-width: 600px) {
|
| 117 |
+
.card, .navbar, .form-control, .form-select, .btn, .alert {
|
| 118 |
+
border-radius: 1.1rem !important;
|
| 119 |
+
}
|
| 120 |
+
.admin-card {
|
| 121 |
+
border-radius: 1.1rem !important;
|
| 122 |
+
}
|
| 123 |
+
.admin-card .card-header {
|
| 124 |
+
border-radius: 1.1rem 1.1rem 0 0 !important;
|
| 125 |
+
}
|
| 126 |
+
.admin-navbar {
|
| 127 |
+
border-radius: 0 0 1.1rem 1.1rem !important;
|
| 128 |
+
margin-top: 0;
|
| 129 |
+
margin-bottom: 0.5rem;
|
| 130 |
+
padding-left: 0.2rem;
|
| 131 |
+
padding-right: 0.2rem;
|
| 132 |
+
}
|
| 133 |
+
.col-md-8, .col-md-4 {
|
| 134 |
+
padding: 0 0.5rem;
|
| 135 |
+
}
|
| 136 |
+
}
|
templates/admin_dashboard.html
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
{% block title %}Admin Dashboard{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<h3 class="mb-4 text-center">Admin Dashboard</h3>
|
| 5 |
+
<form class="row mb-3 justify-content-center" method="get" action="/admin/dashboard">
|
| 6 |
+
<div class="col-md-6 col-12 mb-2 mb-md-0">
|
| 7 |
+
<input type="text" class="form-control" name="search" placeholder="Search by username, name or email" value="{{ search or '' }}">
|
| 8 |
+
</div>
|
| 9 |
+
<div class="col-md-2 col-6">
|
| 10 |
+
<button type="submit" class="btn btn-primary w-100">Search</button>
|
| 11 |
+
</div>
|
| 12 |
+
</form>
|
| 13 |
+
<div class="table-responsive">
|
| 14 |
+
<table class="table table-bordered table-striped align-middle text-center" style="border-radius:1.5rem;overflow:hidden;">
|
| 15 |
+
<thead class="table-light">
|
| 16 |
+
<tr>
|
| 17 |
+
<th>Username</th>
|
| 18 |
+
<th>Name</th>
|
| 19 |
+
<th>Email</th>
|
| 20 |
+
<th>Mobile</th>
|
| 21 |
+
<th>Actions</th>
|
| 22 |
+
</tr>
|
| 23 |
+
</thead>
|
| 24 |
+
<tbody>
|
| 25 |
+
{% for user in users %}
|
| 26 |
+
<tr>
|
| 27 |
+
<td>{{ user.username }}</td>
|
| 28 |
+
<td>{{ user.details.first_name }} {{ user.details.last_name }}</td>
|
| 29 |
+
<td>{{ user.details.email }}</td>
|
| 30 |
+
<td>{{ user.details.mobile }}</td>
|
| 31 |
+
<td>
|
| 32 |
+
<a href="/admin/user/{{ user.id }}" class="btn btn-sm btn-info mx-1 mb-1">View</a>
|
| 33 |
+
<a href="/admin/user/{{ user.id }}/edit" class="btn btn-sm btn-warning mx-1 mb-1">Edit</a>
|
| 34 |
+
<a href="/admin/user/{{ user.id }}/delete" class="btn btn-sm btn-danger mx-1 mb-1" onclick="return confirm('Delete user?');">Delete</a>
|
| 35 |
+
</td>
|
| 36 |
+
</tr>
|
| 37 |
+
{% endfor %}
|
| 38 |
+
</tbody>
|
| 39 |
+
</table>
|
| 40 |
+
</div>
|
| 41 |
+
{% endblock %}
|
templates/admin_edit.html
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
{% block title %}Edit User{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="row justify-content-center">
|
| 5 |
+
<div class="col-md-8" style="padding:0;">
|
| 6 |
+
<form method="post" action="/admin/user/{{ user.id }}/edit" novalidate class="needs-validation" style="background:transparent;">
|
| 7 |
+
<div class="card mb-4 admin-card">
|
| 8 |
+
<div class="card-header bg-primary text-white rounded-top-4">Personal Details</div>
|
| 9 |
+
<div class="card-body row g-3">
|
| 10 |
+
<div class="col-md-6">
|
| 11 |
+
<label class="form-label">First Name</label>
|
| 12 |
+
<input type="text" class="form-control" name="first_name" value="{{ details.first_name or '' }}" required>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="col-md-6">
|
| 15 |
+
<label class="form-label">Last Name</label>
|
| 16 |
+
<input type="text" class="form-control" name="last_name" value="{{ details.last_name or '' }}" required>
|
| 17 |
+
</div>
|
| 18 |
+
<div class="col-md-6">
|
| 19 |
+
<label class="form-label">Email</label>
|
| 20 |
+
<input type="email" class="form-control" name="email" value="{{ details.email or '' }}" required>
|
| 21 |
+
</div>
|
| 22 |
+
<div class="col-md-6">
|
| 23 |
+
<label class="form-label">Mobile No</label>
|
| 24 |
+
<input type="tel" class="form-control" name="mobile" value="{{ details.mobile or '' }}" required pattern="[0-9]{10}">
|
| 25 |
+
</div>
|
| 26 |
+
<div class="col-md-6">
|
| 27 |
+
<label class="form-label">Date of Birth</label>
|
| 28 |
+
<input type="date" class="form-control" name="dob" value="{{ details.dob or '' }}" required>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="col-md-6">
|
| 31 |
+
<label class="form-label">Gender</label>
|
| 32 |
+
<select class="form-select" name="gender" required>
|
| 33 |
+
<option value="">Select</option>
|
| 34 |
+
<option value="Male" {% if details.gender=='Male' %}selected{% endif %}>Male</option>
|
| 35 |
+
<option value="Female" {% if details.gender=='Female' %}selected{% endif %}>Female</option>
|
| 36 |
+
<option value="Other" {% if details.gender=='Other' %}selected{% endif %}>Other</option>
|
| 37 |
+
</select>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
<div class="card mb-4 admin-card">
|
| 42 |
+
<div class="card-header bg-success text-white rounded-top-4">Educational Information</div>
|
| 43 |
+
<div class="card-body row g-3">
|
| 44 |
+
<div class="col-md-6">
|
| 45 |
+
<label class="form-label">Current Semester</label>
|
| 46 |
+
<input type="text" class="form-control" name="current_semester" value="{{ details.current_semester or '' }}" required>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="col-md-6">
|
| 49 |
+
<label class="form-label">Class 10th Percentage</label>
|
| 50 |
+
<input type="number" step="0.01" min="0" max="100" class="form-control" name="tenth_percentage" value="{{ details.tenth_percentage or '' }}" required>
|
| 51 |
+
</div>
|
| 52 |
+
<div class="col-md-6">
|
| 53 |
+
<label class="form-label">Class 12th Percentage</label>
|
| 54 |
+
<input type="number" step="0.01" min="0" max="100" class="form-control" name="twelfth_percentage" value="{{ details.twelfth_percentage or '' }}" required>
|
| 55 |
+
</div>
|
| 56 |
+
<div class="col-md-6">
|
| 57 |
+
<label class="form-label">Graduation Percentage</label>
|
| 58 |
+
<input type="number" step="0.01" min="0" max="100" class="form-control" name="graduation_percentage" value="{{ details.graduation_percentage or '' }}" required>
|
| 59 |
+
</div>
|
| 60 |
+
<div class="col-md-6">
|
| 61 |
+
<label class="form-label">Specialization</label>
|
| 62 |
+
<input type="text" class="form-control" name="specialization" value="{{ details.specialization or '' }}" required>
|
| 63 |
+
</div>
|
| 64 |
+
<div class="col-md-6">
|
| 65 |
+
<label class="form-label">Experience Status</label>
|
| 66 |
+
<select class="form-select" name="experience_status" required>
|
| 67 |
+
<option value="">Select</option>
|
| 68 |
+
<option value="Experienced" {% if details.experience_status=='Experienced' %}selected{% endif %}>Experienced</option>
|
| 69 |
+
<option value="Fresher" {% if details.experience_status=='Fresher' %}selected{% endif %}>Fresher</option>
|
| 70 |
+
</select>
|
| 71 |
+
</div>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
<button type="submit" class="btn btn-primary w-100">Update</button>
|
| 75 |
+
</form>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
<script>
|
| 79 |
+
// Bootstrap validation
|
| 80 |
+
(function () {
|
| 81 |
+
'use strict';
|
| 82 |
+
var forms = document.querySelectorAll('.needs-validation');
|
| 83 |
+
Array.prototype.slice.call(forms).forEach(function (form) {
|
| 84 |
+
form.addEventListener('submit', function (event) {
|
| 85 |
+
if (!form.checkValidity()) {
|
| 86 |
+
event.preventDefault();
|
| 87 |
+
event.stopPropagation();
|
| 88 |
+
}
|
| 89 |
+
form.classList.add('was-validated');
|
| 90 |
+
}, false);
|
| 91 |
+
});
|
| 92 |
+
})();
|
| 93 |
+
</script>
|
| 94 |
+
{% endblock %}
|
templates/admin_view.html
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
{% block title %}View User{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="row justify-content-center">
|
| 5 |
+
<div class="col-md-8" style="padding:0;">
|
| 6 |
+
<div class="card mb-4 admin-card" style="border-radius:1.5rem;">
|
| 7 |
+
<div class="card-header bg-info text-white rounded-top-4">User Details</div>
|
| 8 |
+
<div class="card-body row g-3">
|
| 9 |
+
<div class="col-md-6"><strong>Username:</strong> {{ user.username }}</div>
|
| 10 |
+
<div class="col-md-6"><strong>Name:</strong> {{ details.first_name }} {{ details.last_name }}</div>
|
| 11 |
+
<div class="col-md-6"><strong>Email:</strong> {{ details.email }}</div>
|
| 12 |
+
<div class="col-md-6"><strong>Mobile:</strong> {{ details.mobile }}</div>
|
| 13 |
+
<div class="col-md-6"><strong>Date of Birth:</strong> {{ details.dob }}</div>
|
| 14 |
+
<div class="col-md-6"><strong>Gender:</strong> {{ details.gender }}</div>
|
| 15 |
+
<div class="col-md-6"><strong>Current Semester:</strong> {{ details.current_semester }}</div>
|
| 16 |
+
<div class="col-md-6"><strong>10th </strong> {{ details.tenth_percentage }} %</div>
|
| 17 |
+
<div class="col-md-6"><strong>12th </strong> {{ details.twelfth_percentage }} %</div>
|
| 18 |
+
<div class="col-md-6"><strong>Graduation </strong> {{ details.graduation_percentage }} %</div>
|
| 19 |
+
<div class="col-md-6"><strong>Specialization:</strong> {{ details.specialization }}</div>
|
| 20 |
+
<div class="col-md-6"><strong>Experience:</strong> {{ details.experience_status }}</div>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
<a href="/admin/dashboard" class="btn btn-secondary">Back to Dashboard</a>
|
| 24 |
+
</div>
|
| 25 |
+
</div>
|
| 26 |
+
{% endblock %}
|
templates/base.html
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{% block title %}App{% endblock %}</title>
|
| 7 |
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 8 |
+
<link href="/static/custom.css" rel="stylesheet">
|
| 9 |
+
</head>
|
| 10 |
+
<body>
|
| 11 |
+
<div class="blob-bg"></div>
|
| 12 |
+
<div class="blob-bg2"></div>
|
| 13 |
+
{% if request.session.get('is_admin') %}
|
| 14 |
+
<nav class="navbar navbar-expand-lg navbar-dark admin-navbar">
|
| 15 |
+
<div class="container-fluid">
|
| 16 |
+
<a class="navbar-brand" href="/admin/dashboard">Admin Panel</a>
|
| 17 |
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
| 18 |
+
<span class="navbar-toggler-icon"></span>
|
| 19 |
+
</button>
|
| 20 |
+
<div class="collapse navbar-collapse" id="navbarNav">
|
| 21 |
+
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
| 22 |
+
<li class="nav-item">
|
| 23 |
+
<a class="nav-link" href="/admin/dashboard">Dashboard</a>
|
| 24 |
+
</li>
|
| 25 |
+
<li class="nav-item">
|
| 26 |
+
<a class="nav-link" href="/logout">Logout</a>
|
| 27 |
+
</li>
|
| 28 |
+
</ul>
|
| 29 |
+
</div>
|
| 30 |
+
</div>
|
| 31 |
+
</nav>
|
| 32 |
+
{% else %}
|
| 33 |
+
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
| 34 |
+
<div class="container-fluid">
|
| 35 |
+
<a class="navbar-brand" href="/">Student Portal</a>
|
| 36 |
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
| 37 |
+
<span class="navbar-toggler-icon"></span>
|
| 38 |
+
</button>
|
| 39 |
+
<div class="collapse navbar-collapse" id="navbarNav">
|
| 40 |
+
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
|
| 41 |
+
{% if request.session.get('user_id') %}
|
| 42 |
+
<li class="nav-item">
|
| 43 |
+
<a class="nav-link" href="/user/form">My Form</a>
|
| 44 |
+
</li>
|
| 45 |
+
<li class="nav-item">
|
| 46 |
+
<a class="nav-link" href="/logout">Logout</a>
|
| 47 |
+
</li>
|
| 48 |
+
{% else %}
|
| 49 |
+
<li class="nav-item">
|
| 50 |
+
<a class="nav-link" href="/login">Login</a>
|
| 51 |
+
</li>
|
| 52 |
+
<li class="nav-item">
|
| 53 |
+
<a class="nav-link" href="/signup">Sign Up</a>
|
| 54 |
+
</li>
|
| 55 |
+
{% endif %}
|
| 56 |
+
</ul>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
</nav>
|
| 60 |
+
{% endif %}
|
| 61 |
+
<div class="container mt-4">
|
| 62 |
+
{% if success %}
|
| 63 |
+
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
| 64 |
+
{{ success }}
|
| 65 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
| 66 |
+
</div>
|
| 67 |
+
{% endif %}
|
| 68 |
+
{% if error %}
|
| 69 |
+
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
| 70 |
+
{{ error }}
|
| 71 |
+
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
| 72 |
+
</div>
|
| 73 |
+
{% endif %}
|
| 74 |
+
{% block content %}{% endblock %}
|
| 75 |
+
</div>
|
| 76 |
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
| 77 |
+
</body>
|
| 78 |
+
</html>
|
templates/login.html
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
{% block title %}Login{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="row justify-content-center align-items-center" style="min-height:70vh;">
|
| 5 |
+
<div class="col-md-4 col-12">
|
| 6 |
+
<h3 class="mb-3 text-center">Login</h3>
|
| 7 |
+
<form method="post" action="/login" novalidate class="needs-validation" style="background:transparent;">
|
| 8 |
+
<div class="mb-3">
|
| 9 |
+
<label for="username" class="form-label">Username</label>
|
| 10 |
+
<input type="text" class="form-control" id="username" name="username" required autocomplete="username">
|
| 11 |
+
</div>
|
| 12 |
+
<div class="mb-3">
|
| 13 |
+
<label for="password" class="form-label">Password</label>
|
| 14 |
+
<input type="password" class="form-control" id="password" name="password" required minlength="4" autocomplete="current-password">
|
| 15 |
+
</div>
|
| 16 |
+
<button type="submit" class="btn btn-primary w-100">Login</button>
|
| 17 |
+
</form>
|
| 18 |
+
<div class="mt-3 text-center">
|
| 19 |
+
<a href="/signup">Don't have an account? Sign up</a>
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
</div>
|
| 23 |
+
<script>
|
| 24 |
+
(function () {
|
| 25 |
+
'use strict';
|
| 26 |
+
var forms = document.querySelectorAll('.needs-validation');
|
| 27 |
+
Array.prototype.slice.call(forms).forEach(function (form) {
|
| 28 |
+
form.addEventListener('submit', function (event) {
|
| 29 |
+
if (!form.checkValidity()) {
|
| 30 |
+
event.preventDefault();
|
| 31 |
+
event.stopPropagation();
|
| 32 |
+
}
|
| 33 |
+
form.classList.add('was-validated');
|
| 34 |
+
}, false);
|
| 35 |
+
});
|
| 36 |
+
})();
|
| 37 |
+
</script>
|
| 38 |
+
{% endblock %}
|
templates/signup.html
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
{% block title %}Signup{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="row justify-content-center align-items-center" style="min-height:70vh;">
|
| 5 |
+
<div class="col-md-4 col-12">
|
| 6 |
+
<h3 class="mb-3 text-center">Sign Up</h3>
|
| 7 |
+
<form method="post" action="/signup" novalidate class="needs-validation" style="background:transparent;">
|
| 8 |
+
<div class="mb-3">
|
| 9 |
+
<label for="username" class="form-label">Username</label>
|
| 10 |
+
<input type="text" class="form-control" id="username" name="username" required autocomplete="username">
|
| 11 |
+
</div>
|
| 12 |
+
<div class="mb-3">
|
| 13 |
+
<label for="password" class="form-label">Password</label>
|
| 14 |
+
<input type="password" class="form-control" id="password" name="password" required minlength="4" autocomplete="new-password">
|
| 15 |
+
</div>
|
| 16 |
+
<div class="mb-3">
|
| 17 |
+
<label for="confirm_password" class="form-label">Confirm Password</label>
|
| 18 |
+
<input type="password" class="form-control" id="confirm_password" name="confirm_password" required minlength="4" autocomplete="new-password">
|
| 19 |
+
<div class="invalid-feedback" id="confirm-feedback">Passwords do not match.</div>
|
| 20 |
+
</div>
|
| 21 |
+
<button type="submit" class="btn btn-success w-100" id="signup-btn" disabled>Sign Up</button>
|
| 22 |
+
</form>
|
| 23 |
+
<div class="mt-3 text-center">
|
| 24 |
+
<a href="/login">Already have an account? Login</a>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
<script>
|
| 29 |
+
(function () {
|
| 30 |
+
'use strict';
|
| 31 |
+
var forms = document.querySelectorAll('.needs-validation');
|
| 32 |
+
Array.prototype.slice.call(forms).forEach(function (form) {
|
| 33 |
+
var password = form.querySelector('#password');
|
| 34 |
+
var confirm = form.querySelector('#confirm_password');
|
| 35 |
+
var btn = form.querySelector('#signup-btn');
|
| 36 |
+
var feedback = form.querySelector('#confirm-feedback');
|
| 37 |
+
function checkMatch() {
|
| 38 |
+
if (password.value && confirm.value && password.value === confirm.value) {
|
| 39 |
+
confirm.classList.remove('is-invalid');
|
| 40 |
+
confirm.classList.add('is-valid');
|
| 41 |
+
feedback.style.display = 'none';
|
| 42 |
+
btn.disabled = false;
|
| 43 |
+
} else {
|
| 44 |
+
confirm.classList.remove('is-valid');
|
| 45 |
+
if (confirm.value) {
|
| 46 |
+
confirm.classList.add('is-invalid');
|
| 47 |
+
feedback.style.display = '';
|
| 48 |
+
} else {
|
| 49 |
+
confirm.classList.remove('is-invalid');
|
| 50 |
+
feedback.style.display = 'none';
|
| 51 |
+
}
|
| 52 |
+
btn.disabled = true;
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
password.addEventListener('input', checkMatch);
|
| 56 |
+
confirm.addEventListener('input', checkMatch);
|
| 57 |
+
form.addEventListener('submit', function (event) {
|
| 58 |
+
if (!form.checkValidity() || password.value !== confirm.value) {
|
| 59 |
+
event.preventDefault();
|
| 60 |
+
event.stopPropagation();
|
| 61 |
+
checkMatch();
|
| 62 |
+
}
|
| 63 |
+
form.classList.add('was-validated');
|
| 64 |
+
}, false);
|
| 65 |
+
});
|
| 66 |
+
})();
|
| 67 |
+
</script>
|
| 68 |
+
{% endblock %}
|
templates/user_form.html
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends 'base.html' %}
|
| 2 |
+
{% block title %}Your Details{% endblock %}
|
| 3 |
+
{% block content %}
|
| 4 |
+
<div class="row justify-content-center">
|
| 5 |
+
<div class="col-md-8" style="padding:0;">
|
| 6 |
+
<form method="post" action="/user/form" novalidate class="needs-validation" style="background:transparent;">
|
| 7 |
+
<div class="card mb-4" style="border-radius:1.5rem;">
|
| 8 |
+
<div class="card-header bg-primary text-white">Personal Details</div>
|
| 9 |
+
<div class="card-body row g-3">
|
| 10 |
+
<div class="col-md-6">
|
| 11 |
+
<label class="form-label">First Name</label>
|
| 12 |
+
<input type="text" class="form-control" name="first_name" value="{{ details.first_name or '' }}" required>
|
| 13 |
+
</div>
|
| 14 |
+
<div class="col-md-6">
|
| 15 |
+
<label class="form-label">Last Name</label>
|
| 16 |
+
<input type="text" class="form-control" name="last_name" value="{{ details.last_name or '' }}" required>
|
| 17 |
+
</div>
|
| 18 |
+
<div class="col-md-6">
|
| 19 |
+
<label class="form-label">Email</label>
|
| 20 |
+
<input type="email" class="form-control" name="email" value="{{ details.email or '' }}" required>
|
| 21 |
+
</div>
|
| 22 |
+
<div class="col-md-6">
|
| 23 |
+
<label class="form-label">Mobile No</label>
|
| 24 |
+
<input type="tel" class="form-control" name="mobile" value="{{ details.mobile or '' }}" required pattern="[0-9]{10}">
|
| 25 |
+
</div>
|
| 26 |
+
<div class="col-md-6">
|
| 27 |
+
<label class="form-label">Date of Birth</label>
|
| 28 |
+
<input type="date" class="form-control" name="dob" value="{{ details.dob.strftime('%Y-%m-%d') if details.dob else '' }}" required>
|
| 29 |
+
</div>
|
| 30 |
+
<div class="col-md-6">
|
| 31 |
+
<label class="form-label">Gender</label>
|
| 32 |
+
<select class="form-select" name="gender" required>
|
| 33 |
+
<option value="">Select</option>
|
| 34 |
+
<option value="Male" {% if details.gender=='Male' %}selected{% endif %}>Male</option>
|
| 35 |
+
<option value="Female" {% if details.gender=='Female' %}selected{% endif %}>Female</option>
|
| 36 |
+
<option value="Other" {% if details.gender=='Other' %}selected{% endif %}>Other</option>
|
| 37 |
+
</select>
|
| 38 |
+
</div>
|
| 39 |
+
</div>
|
| 40 |
+
</div>
|
| 41 |
+
<div class="card mb-4" style="border-radius:1.5rem;">
|
| 42 |
+
<div class="card-header bg-success text-white">Educational Information</div>
|
| 43 |
+
<div class="card-body row g-3">
|
| 44 |
+
<div class="col-md-6">
|
| 45 |
+
<label class="form-label">Current Semester</label>
|
| 46 |
+
<input type="text" class="form-control" name="current_semester" value="{{ details.current_semester or '' }}" required>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="col-md-6">
|
| 49 |
+
<label class="form-label">Class 10th Percentage</label>
|
| 50 |
+
<input type="number" step="0.01" min="0" max="100" class="form-control" name="tenth_percentage" value="{{ details.tenth_percentage or '' }}" required>
|
| 51 |
+
</div>
|
| 52 |
+
<div class="col-md-6">
|
| 53 |
+
<label class="form-label">Class 12th Percentage</label>
|
| 54 |
+
<input type="number" step="0.01" min="0" max="100" class="form-control" name="twelfth_percentage" value="{{ details.twelfth_percentage or '' }}" required>
|
| 55 |
+
</div>
|
| 56 |
+
<div class="col-md-6">
|
| 57 |
+
<label class="form-label">Graduation Percentage</label>
|
| 58 |
+
<input type="number" step="0.01" min="0" max="100" class="form-control" name="graduation_percentage" value="{{ details.graduation_percentage or '' }}" required>
|
| 59 |
+
</div>
|
| 60 |
+
<div class="col-md-6">
|
| 61 |
+
<label class="form-label">Specialization</label>
|
| 62 |
+
<input type="text" class="form-control" name="specialization" value="{{ details.specialization or '' }}" required>
|
| 63 |
+
</div>
|
| 64 |
+
<div class="col-md-6">
|
| 65 |
+
<label class="form-label">Experience Status</label>
|
| 66 |
+
<select class="form-select" name="experience_status" required>
|
| 67 |
+
<option value="">Select</option>
|
| 68 |
+
<option value="Experienced" {% if details.experience_status=='Experienced' %}selected{% endif %}>Experienced</option>
|
| 69 |
+
<option value="Fresher" {% if details.experience_status=='Fresher' %}selected{% endif %}>Fresher</option>
|
| 70 |
+
</select>
|
| 71 |
+
</div>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
<button type="submit" class="btn btn-primary w-100">{{ 'Update' if details and details.first_name else 'Submit' }}</button>
|
| 75 |
+
</form>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
<script>
|
| 79 |
+
(function () {
|
| 80 |
+
'use strict';
|
| 81 |
+
var forms = document.querySelectorAll('.needs-validation');
|
| 82 |
+
Array.prototype.slice.call(forms).forEach(function (form) {
|
| 83 |
+
form.addEventListener('submit', function (event) {
|
| 84 |
+
if (!form.checkValidity()) {
|
| 85 |
+
event.preventDefault();
|
| 86 |
+
event.stopPropagation();
|
| 87 |
+
}
|
| 88 |
+
form.classList.add('was-validated');
|
| 89 |
+
}, false);
|
| 90 |
+
});
|
| 91 |
+
})();
|
| 92 |
+
</script>
|
| 93 |
+
{% endblock %}
|