Initial commit: Tablet lending system with user-tablet many-to-many relationship
- Database schema: tablets, users, loans (junction table) - CLI app: minimal_app.py - Web apps: app.py (Flask), simple_app.py (http.server) - Features: loan management, search, user loans view, project notes - Git structure: CONTRIBUTING.md, .gitignore Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
commit
8a8d2f02fa
25 changed files with 2885 additions and 0 deletions
330
app.py
Normal file
330
app.py
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Tablet Lending and Return Management Web Application
|
||||
with SQLite backend
|
||||
"""
|
||||
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash
|
||||
import sqlite3
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = 'your_secret_key_here'
|
||||
|
||||
# Database setup
|
||||
DATABASE = 'tablets.db'
|
||||
|
||||
def get_db():
|
||||
"""Get database connection"""
|
||||
conn = sqlite3.connect(DATABASE)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def init_db():
|
||||
"""Initialize database with required tables"""
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create tablets table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS tablets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
brand TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
serial_number TEXT UNIQUE NOT NULL,
|
||||
status TEXT DEFAULT 'available',
|
||||
notes TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
# Create users table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT,
|
||||
phone TEXT,
|
||||
identification TEXT UNIQUE
|
||||
)
|
||||
''')
|
||||
|
||||
# Create loans table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS loans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tablet_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
loan_date TEXT NOT NULL,
|
||||
return_date TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
FOREIGN KEY (tablet_id) REFERENCES tablets (id),
|
||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Main page showing available tablets and active loans"""
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get available tablets
|
||||
cursor.execute("SELECT * FROM tablets WHERE status = 'available'")
|
||||
available_tablets = cursor.fetchall()
|
||||
|
||||
# Get active loans
|
||||
cursor.execute('''
|
||||
SELECT l.id, t.brand, t.model, t.serial_number, u.name, l.loan_date
|
||||
FROM loans l
|
||||
JOIN tablets t ON l.tablet_id = t.id
|
||||
JOIN users u ON l.user_id = u.id
|
||||
WHERE l.status = 'active'
|
||||
''')
|
||||
active_loans = cursor.fetchall()
|
||||
|
||||
return render_template('index.html',
|
||||
available_tablets=available_tablets,
|
||||
active_loans=active_loans)
|
||||
|
||||
@app.route('/add_tablet', methods=['GET', 'POST'])
|
||||
def add_tablet():
|
||||
"""Add a new tablet to inventory"""
|
||||
if request.method == 'POST':
|
||||
brand = request.form['brand']
|
||||
model = request.form['model']
|
||||
serial_number = request.form['serial_number']
|
||||
notes = request.form['notes']
|
||||
|
||||
try:
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO tablets (brand, model, serial_number, status, notes)
|
||||
VALUES (?, ?, ?, 'available', ?)
|
||||
''', (brand, model, serial_number, notes))
|
||||
conn.commit()
|
||||
flash('Tablet added successfully!', 'success')
|
||||
except sqlite3.IntegrityError:
|
||||
flash('Error: Serial number already exists!', 'error')
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
return render_template('add_tablet.html')
|
||||
|
||||
@app.route('/add_user', methods=['GET', 'POST'])
|
||||
def add_user():
|
||||
"""Add a new user"""
|
||||
if request.method == 'POST':
|
||||
name = request.form['name']
|
||||
email = request.form['email']
|
||||
phone = request.form['phone']
|
||||
identification = request.form['identification']
|
||||
|
||||
try:
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
INSERT INTO users (name, email, phone, identification)
|
||||
VALUES (?, ?, ?, ?)
|
||||
''', (name, email, phone, identification))
|
||||
conn.commit()
|
||||
flash('User added successfully!', 'success')
|
||||
except sqlite3.IntegrityError:
|
||||
flash('Error: Identification already exists!', 'error')
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
return render_template('add_user.html')
|
||||
|
||||
@app.route('/loan_tablet', methods=['GET', 'POST'])
|
||||
def loan_tablet():
|
||||
"""Loan a tablet to a user"""
|
||||
if request.method == 'POST':
|
||||
tablet_id = request.form['tablet_id']
|
||||
user_id = request.form['user_id']
|
||||
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Update tablet status
|
||||
cursor.execute("UPDATE tablets SET status = 'loaned' WHERE id = ?", (tablet_id,))
|
||||
|
||||
# Create loan record
|
||||
loan_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
cursor.execute('''
|
||||
INSERT INTO loans (tablet_id, user_id, loan_date, status)
|
||||
VALUES (?, ?, ?, 'active')
|
||||
''', (tablet_id, user_id, loan_date))
|
||||
|
||||
conn.commit()
|
||||
flash('Tablet loaned successfully!', 'success')
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
# Get data for form
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT * FROM tablets WHERE status = 'available'")
|
||||
available_tablets = cursor.fetchall()
|
||||
cursor.execute("SELECT * FROM users")
|
||||
users = cursor.fetchall()
|
||||
|
||||
return render_template('loan_tablet.html',
|
||||
available_tablets=available_tablets,
|
||||
users=users)
|
||||
|
||||
@app.route('/return_tablet/<int:loan_id>')
|
||||
def return_tablet(loan_id):
|
||||
"""Return a loaned tablet"""
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get loan information
|
||||
cursor.execute("SELECT tablet_id FROM loans WHERE id = ?", (loan_id,))
|
||||
loan = cursor.fetchone()
|
||||
|
||||
if loan:
|
||||
tablet_id = loan['tablet_id']
|
||||
|
||||
# Update loan status and return date
|
||||
return_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
cursor.execute('''
|
||||
UPDATE loans SET status = 'returned', return_date = ? WHERE id = ?
|
||||
''', (return_date, loan_id))
|
||||
|
||||
# Update tablet status
|
||||
cursor.execute("UPDATE tablets SET status = 'available' WHERE id = ?", (tablet_id,))
|
||||
|
||||
conn.commit()
|
||||
flash('Tablet returned successfully!', 'success')
|
||||
else:
|
||||
flash('Error: Loan not found!', 'error')
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
@app.route('/history')
|
||||
def history():
|
||||
"""Show loan history"""
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT l.id, t.brand, t.model, t.serial_number, u.name,
|
||||
l.loan_date, l.return_date, l.status
|
||||
FROM loans l
|
||||
JOIN tablets t ON l.tablet_id = t.id
|
||||
JOIN users u ON l.user_id = u.id
|
||||
ORDER BY l.loan_date DESC
|
||||
''')
|
||||
loans = cursor.fetchall()
|
||||
|
||||
return render_template('history.html', loans=loans)
|
||||
|
||||
|
||||
@app.route('/project_management')
|
||||
def project_management():
|
||||
"""Project management page with markdown editor for development notes"""
|
||||
return render_template('project_management.html')
|
||||
|
||||
|
||||
@app.route('/get_notes/<path:filename>')
|
||||
def get_notes(filename):
|
||||
"""Serve notes file content"""
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
return content, 200, {
|
||||
'Content-Type': 'text/markdown; charset=utf-8',
|
||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
||||
'Pragma': 'no-cache',
|
||||
'Expires': '0'
|
||||
}
|
||||
except FileNotFoundError:
|
||||
return '', 404
|
||||
except Exception as e:
|
||||
return str(e), 500
|
||||
|
||||
|
||||
@app.route('/save_notes', methods=['POST'])
|
||||
def save_notes():
|
||||
"""Save markdown notes to file"""
|
||||
data = request.get_json()
|
||||
content = data.get('content', '')
|
||||
filename = data.get('filename', 'notes_development/project_notes.md')
|
||||
|
||||
try:
|
||||
# Ensure directory exists
|
||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||
|
||||
# Write content to file
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
return {'status': 'success', 'message': 'Notes saved successfully'}
|
||||
except Exception as e:
|
||||
return {'status': 'error', 'message': str(e)}, 500
|
||||
|
||||
|
||||
@app.route('/user_loans')
|
||||
def user_loans():
|
||||
"""
|
||||
Show all loans grouped by user.
|
||||
This demonstrates the one-to-many relationship: one user can loan multiple tablets.
|
||||
"""
|
||||
with get_db() as conn:
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get all users
|
||||
cursor.execute("SELECT * FROM users")
|
||||
users = cursor.fetchall()
|
||||
|
||||
# Get all loans with tablet and user details
|
||||
cursor.execute('''
|
||||
SELECT l.id, l.tablet_id, l.user_id, l.loan_date, l.return_date, l.status,
|
||||
t.brand, t.model, t.serial_number,
|
||||
u.name, u.identification
|
||||
FROM loans l
|
||||
JOIN tablets t ON l.tablet_id = t.id
|
||||
JOIN users u ON l.user_id = u.id
|
||||
ORDER BY u.name, l.loan_date DESC
|
||||
''')
|
||||
loans = cursor.fetchall()
|
||||
|
||||
# Group loans by user
|
||||
user_loans_map = {}
|
||||
for loan in loans:
|
||||
user_id = loan['user_id']
|
||||
if user_id not in user_loans_map:
|
||||
user_loans_map[user_id] = {
|
||||
'user': None,
|
||||
'loans': []
|
||||
}
|
||||
user_loans_map[user_id]['loans'].append(loan)
|
||||
|
||||
# Attach user info
|
||||
for user in users:
|
||||
if user['id'] in user_loans_map:
|
||||
user_loans_map[user['id']]['user'] = user
|
||||
|
||||
# Filter out users with no loans (optional - keep all users)
|
||||
# Convert to list for template
|
||||
user_loans_list = []
|
||||
for user in users:
|
||||
user_data = user_loans_map.get(user['id'], {'user': user, 'loans': []})
|
||||
user_loans_list.append(user_data)
|
||||
|
||||
return render_template('user_loans.html', users_with_loans=user_loans_list)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initialize database
|
||||
init_db()
|
||||
|
||||
# Create templates directory if it doesn't exist
|
||||
if not os.path.exists('templates'):
|
||||
os.makedirs('templates')
|
||||
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
Loading…
Add table
Add a link
Reference in a new issue