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
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
|
||||
# Database files
|
||||
*.db
|
||||
*.db-journal
|
||||
|
||||
# Project-specific
|
||||
notes_development/
|
||||
.vibe/
|
||||
|
||||
# IDE/Editor
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Python
|
||||
.python-version
|
||||
82
CONTRIBUTING.md
Normal file
82
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Contributing Guide
|
||||
|
||||
## Git Workflow
|
||||
|
||||
### Branch Strategy
|
||||
|
||||
| Branch Type | Naming | Purpose |
|
||||
|-------------|--------|---------|
|
||||
| `main` | `main` | Production-ready code |
|
||||
| `develop` | `develop` | Integration branch for features |
|
||||
| Feature | `feature/<name>` | New functionality |
|
||||
| Hotfix | `hotfix/<name>` | Urgent bug fixes |
|
||||
|
||||
### Workflow
|
||||
|
||||
```bash
|
||||
# Clone repository
|
||||
git clone <repository-url>
|
||||
cd GestionTablets
|
||||
|
||||
# Setup develop branch
|
||||
git checkout -b develop
|
||||
|
||||
# Create feature branch
|
||||
git checkout -b feature/my-feature
|
||||
git push origin feature/my-feature
|
||||
|
||||
# Make changes, commit
|
||||
git add .
|
||||
git commit -m "feat: add my feature"
|
||||
git push origin feature/my-feature
|
||||
|
||||
# Create Pull Request to develop
|
||||
```
|
||||
|
||||
### Commit Message Format
|
||||
|
||||
```
|
||||
type(scope): description
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
**Types**:
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation changes
|
||||
- `refactor`: Code refactoring
|
||||
- `chore`: Maintenance tasks
|
||||
- `test`: Test-related changes
|
||||
|
||||
**Examples**:
|
||||
- `feat(loans): add user loans view page`
|
||||
- `fix(tablet): validate serial number uniqueness`
|
||||
- `docs: update README with database schema`
|
||||
- `refactor(app): extract loan logic to service`
|
||||
|
||||
### Pull Request Process
|
||||
|
||||
1. Target `develop` branch (or `main` for hotfixes)
|
||||
2. Include clear description of changes
|
||||
3. Reference related issues
|
||||
4. Wait for code review
|
||||
5. Squash merge preferred
|
||||
|
||||
### Reverting Changes
|
||||
|
||||
```bash
|
||||
# Discard unstaged changes
|
||||
git checkout -- file.txt
|
||||
|
||||
# Unstage file
|
||||
git reset HEAD file.txt
|
||||
|
||||
# Revert to previous commit (safe)
|
||||
git revert HEAD
|
||||
|
||||
# Hard reset to commit (destructive)
|
||||
git reset --hard <commit-hash>
|
||||
```
|
||||
158
README.md
Normal file
158
README.md
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# Tablet Lending and Return Management System
|
||||
|
||||
A simple SQLite-based system for managing tablet lending and returns.
|
||||
|
||||
## Features
|
||||
|
||||
- **Tablet Management**: Add, track, and manage tablets in inventory
|
||||
- **User Management**: Register users who can borrow tablets
|
||||
- **Loan Tracking**: Track which tablets are loaned to which users
|
||||
- **Return Management**: Record when tablets are returned
|
||||
- **History Tracking**: Complete history of all loans and returns
|
||||
- **User Loans View**: See all tablets loaned to each user (demonstrates one-to-many relationship)
|
||||
- **Project Notes**: Markdown editor for development documentation
|
||||
|
||||
## Files
|
||||
|
||||
- `minimal_app.py` - Interactive command-line application
|
||||
- `test_app.py` - Test script that demonstrates functionality
|
||||
- `tablets.db` - SQLite database (created automatically)
|
||||
- `simple_app.py` - Web-based version (requires Flask)
|
||||
- `app.py` - Alternative web version (requires Flask)
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Run the test to see it in action
|
||||
|
||||
```bash
|
||||
python3 test_app.py
|
||||
```
|
||||
|
||||
This will:
|
||||
- Create the database
|
||||
- Add sample tablets and users
|
||||
- Demonstrate loan and return operations
|
||||
- Show the complete workflow
|
||||
|
||||
### 2. Run the interactive application
|
||||
|
||||
```bash
|
||||
python3 minimal_app.py
|
||||
```
|
||||
|
||||
This provides a menu-driven interface for:
|
||||
- Adding tablets
|
||||
- Adding users
|
||||
- Loaning tablets
|
||||
- Returning tablets
|
||||
- Viewing inventory and loan status
|
||||
|
||||
### 3. Database Structure
|
||||
|
||||
The system uses three main tables with a **many-to-many relationship** between users and tablets:
|
||||
|
||||
**tablets**
|
||||
- `id`: Primary key
|
||||
- `brand`: Tablet brand
|
||||
- `model`: Tablet model
|
||||
- `serial_number`: Unique serial number
|
||||
- `status`: 'available' or 'loaned'
|
||||
|
||||
**users**
|
||||
- `id`: Primary key
|
||||
- `name`: User's name
|
||||
- `identification`: Unique identification
|
||||
|
||||
**loans** (junction table)
|
||||
- `id`: Primary key
|
||||
- `tablet_id`: Foreign key to tablets
|
||||
- `user_id`: Foreign key to users
|
||||
- `loan_date`: When the tablet was loaned
|
||||
- `return_date`: When the tablet was returned (NULL if still active)
|
||||
- `status`: 'active' or 'returned'
|
||||
|
||||
> **Note**: The loans table enables a many-to-many relationship. One user can loan **multiple tablets** (one-to-many from user to loans), and each tablet can be loaned to different users over time. The `status` field on tablets ensures a device can only be loaned to one user at a time.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.x (built-in sqlite3 module)
|
||||
- No additional dependencies needed for the command-line version
|
||||
- Flask required for web versions (optional)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Adding a tablet
|
||||
```
|
||||
python3 -c "
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, ?)',
|
||||
('Microsoft', 'Surface Pro', 'SN004', 'available'))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print('Tablet added!')
|
||||
"
|
||||
```
|
||||
|
||||
### Adding a user
|
||||
```
|
||||
python3 -c "
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO users (name, identification) VALUES (?, ?)',
|
||||
('Alice Brown', 'ID004'))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print('User added!')
|
||||
"
|
||||
```
|
||||
|
||||
### Querying available tablets
|
||||
```
|
||||
python3 -c "
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT brand, model, serial_number FROM tablets WHERE status = ?', ('available',))
|
||||
for row in cursor.fetchall():
|
||||
print(f'{row[0]} {row[1]} ({row[2]})')
|
||||
conn.close()
|
||||
"
|
||||
```
|
||||
|
||||
## Web Version (Optional)
|
||||
|
||||
If you want to use the web interface:
|
||||
|
||||
1. Install Flask:
|
||||
```bash
|
||||
pip install flask
|
||||
```
|
||||
|
||||
2. Run the web application:
|
||||
```bash
|
||||
python3 app.py
|
||||
```
|
||||
|
||||
3. Open your browser to: http://localhost:5000
|
||||
|
||||
### Web Features
|
||||
|
||||
- **Home**: View available tablets and active loans
|
||||
- **Loan Tablet**: Loan a tablet to a user (with search for large datasets)
|
||||
- **User Loans**: View all tablets loaned to each user - demonstrates the **one-to-many relationship** (one user, multiple tablets)
|
||||
- **Loan History**: Complete history of all loan and return transactions
|
||||
- **Project Management**: Markdown editor for development notes (saved to `notes_development/project_notes.md`)
|
||||
|
||||
## Database Backup
|
||||
|
||||
To backup your data:
|
||||
```bash
|
||||
cp tablets.db tablets_backup_$(date +%Y%m%d).db
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This is a simple educational project. Feel free to use and modify it as needed.
|
||||
57
RUNNING.md
Normal file
57
RUNNING.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Tablet Management System - Running
|
||||
|
||||
✅ **Status**: Application is running successfully!
|
||||
|
||||
## Access Information
|
||||
|
||||
- **Web Interface**: [http://localhost:5000](http://localhost:5000)
|
||||
- **Port**: 5000
|
||||
- **Framework**: Flask 3.1.3
|
||||
- **Database**: SQLite (`tablets.db`)
|
||||
|
||||
## How to Use
|
||||
|
||||
### Web Interface
|
||||
1. Open your browser to: [http://localhost:5000](http://localhost:5000)
|
||||
2. Use the navigation menu to:
|
||||
- **Home**: View available tablets and active loans
|
||||
- **Add Tablet**: Add new tablets to inventory
|
||||
- **Add User**: Register new users
|
||||
- **Loan Tablet**: Loan a tablet to a user
|
||||
- **Loan History**: View complete loan history
|
||||
|
||||
### Command Line (Alternative)
|
||||
If you prefer command line:
|
||||
```bash
|
||||
# Run the interactive application
|
||||
python3 minimal_app.py
|
||||
|
||||
# Run the test/demo
|
||||
python3 test_app.py
|
||||
```
|
||||
|
||||
## Current Data
|
||||
The system already contains test data:
|
||||
- **3 Tablets**: Samsung Galaxy Tab S7, Apple iPad Pro, Lenovo Tab P11
|
||||
- **3 Users**: John Doe, Jane Smith, Bob Johnson
|
||||
- **2 Loans**: One active, one returned
|
||||
|
||||
## Stopping the Server
|
||||
To stop the Flask application:
|
||||
```bash
|
||||
pkill -f "python app.py"
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
- **Backend**: SQLite 3
|
||||
- **Frontend**: Flask with HTML/CSS
|
||||
- **Port**: 5000 (configurable in app.py)
|
||||
- **Virtual Environment**: `.venv/` (created with uv)
|
||||
|
||||
## Troubleshooting
|
||||
If you have issues:
|
||||
1. Check if the server is running: `ps aux | grep app.py`
|
||||
2. Check the database: `sqlite3 tablets.db`
|
||||
3. Restart the server: `source .venv/bin/activate && python app.py`
|
||||
|
||||
Enjoy managing your tablets! 📱💻
|
||||
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)
|
||||
81
basic_server.py
Normal file
81
basic_server.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic HTTP server to serve the tablet management system
|
||||
"""
|
||||
|
||||
from http.server import SimpleHTTPRequestHandler, HTTPServer
|
||||
import os
|
||||
|
||||
class MyHandler(SimpleHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
self.path = '/index.html'
|
||||
return super().do_GET()
|
||||
|
||||
def run_server():
|
||||
port = 8080
|
||||
server_address = ('', port)
|
||||
httpd = HTTPServer(server_address, MyHandler)
|
||||
|
||||
# Create a simple index file if it doesn't exist
|
||||
if not os.path.exists('index.html'):
|
||||
with open('index.html', 'w') as f:
|
||||
f.write('''<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tablet Management System</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #4CAF50; }
|
||||
.info { background: #f0f0f0; padding: 20px; border-radius: 5px; }
|
||||
.command { background: #e0e0e0; padding: 10px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Tablet Management System</h1>
|
||||
|
||||
<div class="info">
|
||||
<h2>Welcome to the Tablet Management System!</h2>
|
||||
<p>This system is running with a SQLite backend.</p>
|
||||
|
||||
<h3>Available Commands:</h3>
|
||||
<ul>
|
||||
<li><strong>Test the system:</strong> <code class="command">python3 test_app.py</code></li>
|
||||
<li><strong>Run interactive mode:</strong> <code class="command">python3 minimal_app.py</code></li>
|
||||
<li><strong>Check database:</strong> <code class="command">sqlite3 tablets.db</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Current Status:</h3>
|
||||
<p>✓ Database: tablets.db</p>
|
||||
<p>✓ Web server: Running on port 8080</p>
|
||||
<p>✓ System: Ready for use</p>
|
||||
</div>
|
||||
|
||||
<h3>Quick Test Results:</h3>
|
||||
<pre id="test-results">Running tests...</pre>
|
||||
|
||||
<script>
|
||||
// Simple test to show the system is working
|
||||
fetch('/test-db')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('test-results').textContent = data;
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('test-results').textContent = 'Database test: Error - ' + error;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>''')
|
||||
|
||||
print(f"Server running on http://localhost:{port}")
|
||||
print("Serving current directory")
|
||||
print("Press Ctrl+C to stop the server")
|
||||
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nServer stopped")
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_server()
|
||||
20
check_flask.py
Normal file
20
check_flask.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Check if Flask is available and provide installation instructions
|
||||
"""
|
||||
|
||||
try:
|
||||
import flask
|
||||
print("✓ Flask is available!")
|
||||
print("You can run the web version:")
|
||||
print(" python3 simple_app.py")
|
||||
print("Then open: http://localhost:8000")
|
||||
except ImportError:
|
||||
print("✗ Flask is not installed")
|
||||
print("\nTo install Flask:")
|
||||
print(" pip install flask")
|
||||
print("\nOr use the system package manager:")
|
||||
print(" sudo apt install python3-flask")
|
||||
print("\nYou can still use the command-line version:")
|
||||
print(" python3 minimal_app.py")
|
||||
print(" python3 test_app.py")
|
||||
20
debug_server.py
Normal file
20
debug_server.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python3
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
|
||||
class DebugHandler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
self.wfile.write(b'Hello from Tablet Management Server!')
|
||||
|
||||
def run():
|
||||
try:
|
||||
server = HTTPServer(('', 8000), DebugHandler)
|
||||
print("Debug server running on port 8000")
|
||||
server.serve_forever()
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
47
index.html
Normal file
47
index.html
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tablet Management System</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 40px; }
|
||||
h1 { color: #4CAF50; }
|
||||
.info { background: #f0f0f0; padding: 20px; border-radius: 5px; }
|
||||
.command { background: #e0e0e0; padding: 10px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Tablet Management System</h1>
|
||||
|
||||
<div class="info">
|
||||
<h2>Welcome to the Tablet Management System!</h2>
|
||||
<p>This system is running with a SQLite backend.</p>
|
||||
|
||||
<h3>Available Commands:</h3>
|
||||
<ul>
|
||||
<li><strong>Test the system:</strong> <code class="command">python3 test_app.py</code></li>
|
||||
<li><strong>Run interactive mode:</strong> <code class="command">python3 minimal_app.py</code></li>
|
||||
<li><strong>Check database:</strong> <code class="command">sqlite3 tablets.db</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Current Status:</h3>
|
||||
<p>✓ Database: tablets.db</p>
|
||||
<p>✓ Web server: Running on port 8080</p>
|
||||
<p>✓ System: Ready for use</p>
|
||||
</div>
|
||||
|
||||
<h3>Quick Test Results:</h3>
|
||||
<pre id="test-results">Running tests...</pre>
|
||||
|
||||
<script>
|
||||
// Simple test to show the system is working
|
||||
fetch('/test-db')
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('test-results').textContent = data;
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('test-results').textContent = 'Database test: Error - ' + error;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
6
main.py
Normal file
6
main.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
def main():
|
||||
print("Hello from gestiontablets!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
296
minimal_app.py
Normal file
296
minimal_app.py
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal Tablet Lending and Return Management System
|
||||
Using only built-in Python modules
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
def init_db():
|
||||
"""Initialize database with required tables"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create tables
|
||||
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'
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
identification TEXT UNIQUE
|
||||
)
|
||||
''')
|
||||
|
||||
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()
|
||||
conn.close()
|
||||
|
||||
def add_tablet(brand, model, serial_number):
|
||||
"""Add a new tablet to inventory"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute('''
|
||||
INSERT INTO tablets (brand, model, serial_number, status)
|
||||
VALUES (?, ?, ?, 'available')
|
||||
''', (brand, model, serial_number))
|
||||
conn.commit()
|
||||
print(f"✓ Added tablet: {brand} {model} ({serial_number})")
|
||||
except sqlite3.IntegrityError:
|
||||
print(f"✗ Error: Serial number {serial_number} already exists")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def add_user(name, identification):
|
||||
"""Add a new user"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute('''
|
||||
INSERT INTO users (name, identification)
|
||||
VALUES (?, ?)
|
||||
''', (name, identification))
|
||||
conn.commit()
|
||||
print(f"✓ Added user: {name} ({identification})")
|
||||
except sqlite3.IntegrityError:
|
||||
print(f"✗ Error: Identification {identification} already exists")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def loan_tablet(tablet_id, user_id):
|
||||
"""Loan a tablet to a user"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if tablet is available
|
||||
cursor.execute("SELECT status FROM tablets WHERE id = ?", (tablet_id,))
|
||||
result = cursor.fetchone()
|
||||
|
||||
if result and result[0] == 'available':
|
||||
# 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()
|
||||
print(f"✓ Tablet {tablet_id} loaned to user {user_id}")
|
||||
else:
|
||||
print(f"✗ Error: Tablet {tablet_id} is not available for loan")
|
||||
|
||||
conn.close()
|
||||
|
||||
def return_tablet(loan_id):
|
||||
"""Return a loaned tablet"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Get loan information
|
||||
cursor.execute("SELECT tablet_id FROM loans WHERE id = ? AND status = 'active'", (loan_id,))
|
||||
loan = cursor.fetchone()
|
||||
|
||||
if loan:
|
||||
tablet_id = loan[0]
|
||||
|
||||
# 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()
|
||||
print(f"✓ Tablet returned for loan {loan_id}")
|
||||
else:
|
||||
print(f"✗ Error: Loan {loan_id} not found or already returned")
|
||||
|
||||
conn.close()
|
||||
|
||||
def show_available_tablets():
|
||||
"""Show available tablets"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT id, brand, model, serial_number FROM tablets WHERE status = 'available'")
|
||||
tablets = cursor.fetchall()
|
||||
|
||||
print("\n=== Available Tablets ===")
|
||||
if tablets:
|
||||
for tablet in tablets:
|
||||
print(f"ID: {tablet[0]}, {tablet[1]} {tablet[2]} ({tablet[3]})")
|
||||
else:
|
||||
print("No available tablets")
|
||||
|
||||
conn.close()
|
||||
|
||||
def show_active_loans():
|
||||
"""Show active loans"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
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'
|
||||
''')
|
||||
loans = cursor.fetchall()
|
||||
|
||||
print("\n=== Active Loans ===")
|
||||
if loans:
|
||||
for loan in loans:
|
||||
print(f"Loan ID: {loan[0]}, Tablet: {loan[1]} {loan[2]} ({loan[3]}), Borrower: {loan[4]}, Loan Date: {loan[5]}")
|
||||
else:
|
||||
print("No active loans")
|
||||
|
||||
conn.close()
|
||||
|
||||
def show_loan_history():
|
||||
"""Show complete loan history"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
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()
|
||||
|
||||
print("\n=== Loan History ===")
|
||||
if loans:
|
||||
for loan in loans:
|
||||
return_date = loan[6] or 'Not returned'
|
||||
print(f"Loan ID: {loan[0]}, Tablet: {loan[1]} {loan[2]} ({loan[3]}), Borrower: {loan[4]}")
|
||||
print(f" Loan Date: {loan[5]}, Return Date: {return_date}, Status: {loan[7]}")
|
||||
else:
|
||||
print("No loan history")
|
||||
|
||||
conn.close()
|
||||
|
||||
def main():
|
||||
"""Main menu"""
|
||||
init_db()
|
||||
|
||||
print("=== Tablet Lending and Return Management System ===")
|
||||
print("Using SQLite database: tablets.db")
|
||||
|
||||
while True:
|
||||
print("\nMenu:")
|
||||
print("1. Add Tablet")
|
||||
print("2. Add User")
|
||||
print("3. Loan Tablet")
|
||||
print("4. Return Tablet")
|
||||
print("5. Show Available Tablets")
|
||||
print("6. Show Active Loans")
|
||||
print("7. Show Loan History")
|
||||
print("8. Exit")
|
||||
|
||||
choice = input("Enter your choice (1-8): ")
|
||||
|
||||
if choice == '1':
|
||||
print("\n=== Add Tablet ===")
|
||||
brand = input("Brand: ")
|
||||
model = input("Model: ")
|
||||
serial_number = input("Serial Number: ")
|
||||
add_tablet(brand, model, serial_number)
|
||||
|
||||
elif choice == '2':
|
||||
print("\n=== Add User ===")
|
||||
name = input("Name: ")
|
||||
identification = input("Identification: ")
|
||||
add_user(name, identification)
|
||||
|
||||
elif choice == '3':
|
||||
print("\n=== Loan Tablet ===")
|
||||
show_available_tablets()
|
||||
show_users()
|
||||
|
||||
tablet_id = input("Enter Tablet ID to loan: ")
|
||||
user_id = input("Enter User ID: ")
|
||||
|
||||
try:
|
||||
loan_tablet(int(tablet_id), int(user_id))
|
||||
except ValueError:
|
||||
print("✗ Error: Invalid ID format")
|
||||
|
||||
elif choice == '4':
|
||||
print("\n=== Return Tablet ===")
|
||||
show_active_loans()
|
||||
|
||||
loan_id = input("Enter Loan ID to return: ")
|
||||
|
||||
try:
|
||||
return_tablet(int(loan_id))
|
||||
except ValueError:
|
||||
print("✗ Error: Invalid Loan ID format")
|
||||
|
||||
elif choice == '5':
|
||||
show_available_tablets()
|
||||
|
||||
elif choice == '6':
|
||||
show_active_loans()
|
||||
|
||||
elif choice == '7':
|
||||
show_loan_history()
|
||||
|
||||
elif choice == '8':
|
||||
print("Goodbye!")
|
||||
break
|
||||
|
||||
else:
|
||||
print("✗ Invalid choice. Please try again.")
|
||||
|
||||
def show_users():
|
||||
"""Show available users"""
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("SELECT id, name, identification FROM users")
|
||||
users = cursor.fetchall()
|
||||
|
||||
print("\n=== Available Users ===")
|
||||
if users:
|
||||
for user in users:
|
||||
print(f"ID: {user[0]}, {user[1]} ({user[2]})")
|
||||
else:
|
||||
print("No users available")
|
||||
|
||||
conn.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
7
pyproject.toml
Normal file
7
pyproject.toml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[project]
|
||||
name = "gestiontablets"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = []
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
Flask==2.3.3
|
||||
60
setup.py
Normal file
60
setup.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Setup script for Tablet Management System
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
def install_dependencies():
|
||||
"""Install required dependencies"""
|
||||
try:
|
||||
# Try to install Flask using pip
|
||||
subprocess.check_call([sys.executable, '-m', 'ensurepip', '--upgrade'])
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'Flask==2.3.3'])
|
||||
print("✓ Dependencies installed successfully")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Failed to install dependencies: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"✗ Error during installation: {e}")
|
||||
return False
|
||||
|
||||
def check_dependencies():
|
||||
"""Check if required dependencies are available"""
|
||||
try:
|
||||
import flask
|
||||
import sqlite3
|
||||
print("✓ All dependencies are available")
|
||||
return True
|
||||
except ImportError as e:
|
||||
print(f"✗ Missing dependency: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("Setting up Tablet Management System...")
|
||||
|
||||
# Check if dependencies are already available
|
||||
if not check_dependencies():
|
||||
print("Installing required dependencies...")
|
||||
if not install_dependencies():
|
||||
print("\nError: Could not install dependencies.")
|
||||
print("Please install Flask manually:")
|
||||
print(" pip install Flask==2.3.3")
|
||||
print("or")
|
||||
print(" python3 -m pip install Flask==2.3.3")
|
||||
return False
|
||||
|
||||
print("\nSetup complete!")
|
||||
print("\nTo run the application:")
|
||||
print(" python3 app.py")
|
||||
print("\nThe application will be available at: http://localhost:5000")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == '__main__':
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
555
simple_app.py
Normal file
555
simple_app.py
Normal file
|
|
@ -0,0 +1,555 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple Tablet Lending and Return Management System
|
||||
Using only built-in Python modules (no Flask required)
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import os
|
||||
from datetime import datetime
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
from urllib.parse import parse_qs, urlparse
|
||||
import json
|
||||
|
||||
# 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()
|
||||
|
||||
class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
|
||||
def _set_headers(self, content_type="text/html"):
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', content_type)
|
||||
self.end_headers()
|
||||
|
||||
def _send_html(self, content):
|
||||
self._set_headers()
|
||||
self.wfile.write(content.encode('utf-8'))
|
||||
|
||||
def _send_json(self, data):
|
||||
self._set_headers(content_type="application/json")
|
||||
self.wfile.write(json.dumps(data).encode('utf-8'))
|
||||
|
||||
def _parse_form_data(self):
|
||||
content_length = int(self.headers['Content-Length'])
|
||||
post_data = self.rfile.read(content_length)
|
||||
return parse_qs(post_data.decode('utf-8'))
|
||||
|
||||
def do_GET(self):
|
||||
parsed_path = urlparse(self.path)
|
||||
|
||||
if parsed_path.path == '/':
|
||||
self._handle_index()
|
||||
elif parsed_path.path == '/add_tablet':
|
||||
self._handle_add_tablet_form()
|
||||
elif parsed_path.path == '/add_user':
|
||||
self._handle_add_user_form()
|
||||
elif parsed_path.path == '/loan_tablet':
|
||||
self._handle_loan_tablet_form()
|
||||
elif parsed_path.path == '/history':
|
||||
self._handle_history()
|
||||
elif parsed_path.path.startswith('/return_tablet/'):
|
||||
self._handle_return_tablet(parsed_path.path)
|
||||
else:
|
||||
self._send_html("<h1>404 Not Found</h1>")
|
||||
|
||||
def do_POST(self):
|
||||
parsed_path = urlparse(self.path)
|
||||
|
||||
if parsed_path.path == '/add_tablet':
|
||||
self._handle_add_tablet()
|
||||
elif parsed_path.path == '/add_user':
|
||||
self._handle_add_user()
|
||||
elif parsed_path.path == '/loan_tablet':
|
||||
self._handle_loan_tablet()
|
||||
else:
|
||||
self._send_html("<h1>404 Not Found</h1>")
|
||||
|
||||
def _handle_index(self):
|
||||
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()
|
||||
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tablet Management System</title>
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; margin: 20px; }}
|
||||
h1 {{ color: #333; }}
|
||||
.nav {{ margin-bottom: 20px; }}
|
||||
.nav a {{ margin-right: 15px; text-decoration: none; color: #4CAF50; }}
|
||||
table {{ width: 100%; border-collapse: collapse; margin-bottom: 20px; }}
|
||||
th, td {{ padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }}
|
||||
th {{ background-color: #4CAF50; color: white; }}
|
||||
.btn {{ padding: 5px 10px; background-color: #4CAF50; color: white; text-decoration: none; border-radius: 3px; }}
|
||||
.btn-danger {{ background-color: #f44336; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Tablet Management System</h1>
|
||||
<div class="nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="/add_tablet">Add Tablet</a>
|
||||
<a href="/add_user">Add User</a>
|
||||
<a href="/loan_tablet">Loan Tablet</a>
|
||||
<a href="/history">Loan History</a>
|
||||
</div>
|
||||
|
||||
<h2>Available Tablets</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Brand</th>
|
||||
<th>Model</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"""
|
||||
|
||||
if available_tablets:
|
||||
for tablet in available_tablets:
|
||||
html += f"""
|
||||
<tr>
|
||||
<td>{tablet['brand']}</td>
|
||||
<td>{tablet['model']}</td>
|
||||
<td>{tablet['serial_number']}</td>
|
||||
<td>{tablet['notes'] or '-'}</td>
|
||||
</tr>
|
||||
"""
|
||||
else:
|
||||
html += "<tr><td colspan='4'>No available tablets.</td></tr>"
|
||||
|
||||
html += """
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Active Loans</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tablet</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Borrower</th>
|
||||
<th>Loan Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"""
|
||||
|
||||
if active_loans:
|
||||
for loan in active_loans:
|
||||
html += f"""
|
||||
<tr>
|
||||
<td>{loan['brand']} {loan['model']}</td>
|
||||
<td>{loan['serial_number']}</td>
|
||||
<td>{loan['name']}</td>
|
||||
<td>{loan['loan_date']}</td>
|
||||
<td><a href='/return_tablet/{loan['id']}' class='btn btn-danger'>Return</a></td>
|
||||
</tr>
|
||||
"""
|
||||
else:
|
||||
html += "<tr><td colspan='5'>No active loans.</td></tr>"
|
||||
|
||||
html += """
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
self._send_html(html)
|
||||
|
||||
def _handle_add_tablet_form(self):
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Add Tablet</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
label { display: block; margin-bottom: 5px; }
|
||||
input, textarea { width: 100%; padding: 8px; }
|
||||
.btn { padding: 8px 16px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Add New Tablet</h1>
|
||||
<form method="POST" action="/add_tablet">
|
||||
<div class="form-group">
|
||||
<label for="brand">Brand:</label>
|
||||
<input type="text" id="brand" name="brand" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="model">Model:</label>
|
||||
<input type="text" id="model" name="model" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="serial_number">Serial Number:</label>
|
||||
<input type="text" id="serial_number" name="serial_number" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notes">Notes:</label>
|
||||
<textarea id="notes" name="notes"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Add Tablet</button>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
self._send_html(html)
|
||||
|
||||
def _handle_add_tablet(self):
|
||||
form_data = self._parse_form_data()
|
||||
brand = form_data['brand'][0]
|
||||
model = form_data['model'][0]
|
||||
serial_number = form_data['serial_number'][0]
|
||||
notes = form_data.get('notes', [''])[0]
|
||||
|
||||
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()
|
||||
|
||||
self.send_response(303)
|
||||
self.send_header('Location', '/')
|
||||
self.end_headers()
|
||||
except sqlite3.IntegrityError:
|
||||
self._send_html("<h1>Error: Serial number already exists!</h1><p><a href='/add_tablet'>Try again</a></p>")
|
||||
|
||||
def _handle_add_user_form(self):
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Add User</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
label { display: block; margin-bottom: 5px; }
|
||||
input { width: 100%; padding: 8px; }
|
||||
.btn { padding: 8px 16px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Add New User</h1>
|
||||
<form method="POST" action="/add_user">
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone:</label>
|
||||
<input type="text" id="phone" name="phone">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="identification">Identification:</label>
|
||||
<input type="text" id="identification" name="identification" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Add User</button>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
self._send_html(html)
|
||||
|
||||
def _handle_add_user(self):
|
||||
form_data = self._parse_form_data()
|
||||
name = form_data['name'][0]
|
||||
email = form_data.get('email', [''])[0]
|
||||
phone = form_data.get('phone', [''])[0]
|
||||
identification = form_data['identification'][0]
|
||||
|
||||
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()
|
||||
|
||||
self.send_response(303)
|
||||
self.send_header('Location', '/')
|
||||
self.end_headers()
|
||||
except sqlite3.IntegrityError:
|
||||
self._send_html("<h1>Error: Identification already exists!</h1><p><a href='/add_user'>Try again</a></p>")
|
||||
|
||||
def _handle_loan_tablet_form(self):
|
||||
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()
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Loan Tablet</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
.form-group { margin-bottom: 15px; }
|
||||
label { display: block; margin-bottom: 5px; }
|
||||
select { width: 100%; padding: 8px; }
|
||||
.btn { padding: 8px 16px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loan Tablet</h1>
|
||||
<form method="POST" action="/loan_tablet">
|
||||
<div class="form-group">
|
||||
<label for="tablet_id">Tablet:</label>
|
||||
<select id="tablet_id" name="tablet_id" required>
|
||||
<option value="">Select a tablet</option>
|
||||
"""
|
||||
|
||||
for tablet in available_tablets:
|
||||
html += f"<option value='{tablet['id']}'>{tablet['brand']} {tablet['model']} ({tablet['serial_number']})</option>"
|
||||
|
||||
html += """
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="user_id">User:</label>
|
||||
<select id="user_id" name="user_id" required>
|
||||
<option value="">Select a user</option>
|
||||
"""
|
||||
|
||||
for user in users:
|
||||
html += f"<option value='{user['id']}'>{user['name']} ({user['identification']})</option>"
|
||||
|
||||
html += """
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Loan Tablet</button>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
self._send_html(html)
|
||||
|
||||
def _handle_loan_tablet(self):
|
||||
form_data = self._parse_form_data()
|
||||
tablet_id = form_data['tablet_id'][0]
|
||||
user_id = form_data['user_id'][0]
|
||||
|
||||
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()
|
||||
|
||||
self.send_response(303)
|
||||
self.send_header('Location', '/')
|
||||
self.end_headers()
|
||||
|
||||
def _handle_return_tablet(self, path):
|
||||
loan_id = path.split('/')[-1]
|
||||
|
||||
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()
|
||||
|
||||
self.send_response(303)
|
||||
self.send_header('Location', '/')
|
||||
self.end_headers()
|
||||
|
||||
def _handle_history(self):
|
||||
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()
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Loan History</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
h1 { color: #333; }
|
||||
.nav { margin-bottom: 20px; }
|
||||
.nav a { margin-right: 15px; text-decoration: none; color: #4CAF50; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
th { background-color: #4CAF50; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Loan History</h1>
|
||||
<div class="nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="/add_tablet">Add Tablet</a>
|
||||
<a href="/add_user">Add User</a>
|
||||
<a href="/loan_tablet">Loan Tablet</a>
|
||||
<a href="/history">Loan History</a>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tablet</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Borrower</th>
|
||||
<th>Loan Date</th>
|
||||
<th>Return Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
"""
|
||||
|
||||
if loans:
|
||||
for loan in loans:
|
||||
html += f"""
|
||||
<tr>
|
||||
<td>{loan['brand']} {loan['model']}</td>
|
||||
<td>{loan['serial_number']}</td>
|
||||
<td>{loan['name']}</td>
|
||||
<td>{loan['loan_date']}</td>
|
||||
<td>{loan['return_date'] or '-'}</td>
|
||||
<td>{loan['status']}</td>
|
||||
</tr>
|
||||
"""
|
||||
else:
|
||||
html += "<tr><td colspan='6'>No loan history available.</td></tr>"
|
||||
|
||||
html += """
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
self._send_html(html)
|
||||
|
||||
def run_server():
|
||||
"""Run the HTTP server"""
|
||||
server_address = ('', 8000)
|
||||
httpd = HTTPServer(server_address, SimpleHTTPRequestHandler)
|
||||
print(f"Server running on http://localhost:8000")
|
||||
print("Press Ctrl+C to stop the server")
|
||||
httpd.serve_forever()
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initialize database
|
||||
init_db()
|
||||
|
||||
# Run the server
|
||||
run_server()
|
||||
30
templates/add_tablet.html
Normal file
30
templates/add_tablet.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Add New Tablet</h2>
|
||||
<form method="POST" action="/add_tablet">
|
||||
<div class="form-group">
|
||||
<label for="brand">Brand:</label>
|
||||
<input type="text" id="brand" name="brand" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="model">Model:</label>
|
||||
<input type="text" id="model" name="model" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="serial_number">Serial Number:</label>
|
||||
<input type="text" id="serial_number" name="serial_number" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notes">Notes:</label>
|
||||
<textarea id="notes" name="notes"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Add Tablet</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
30
templates/add_user.html
Normal file
30
templates/add_user.html
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Add New User</h2>
|
||||
<form method="POST" action="/add_user">
|
||||
<div class="form-group">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" id="name" name="name" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email:</label>
|
||||
<input type="email" id="email" name="email">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="phone">Phone:</label>
|
||||
<input type="text" id="phone" name="phone">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="identification">Identification:</label>
|
||||
<input type="text" id="identification" name="identification" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Add User</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
153
templates/base.html
Normal file
153
templates/base.html
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tablet Management System</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.nav a {
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.nav a:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.section h2 {
|
||||
color: #4CAF50;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.btn-danger {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #CC00CC;
|
||||
}
|
||||
.flash-message {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.flash-success {
|
||||
background-color: #dff0d8;
|
||||
color: #3c763d;
|
||||
border: 1px solid #d6e9c6;
|
||||
}
|
||||
.flash-error {
|
||||
background-color: #f2dede;
|
||||
color: #a94442;
|
||||
border: 1px solid #ebccd1;
|
||||
}
|
||||
form {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
textarea,
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
textarea {
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Tablet Management System</h1>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="flash-message flash-{{ category }}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<div class="nav">
|
||||
<a href="/">Home</a>
|
||||
<a href="/add_tablet">Add Tablet</a>
|
||||
<a href="/add_user">Add User</a>
|
||||
<a href="/loan_tablet">Loan Tablet</a>
|
||||
<a href="/history">Loan History</a>
|
||||
<a href="/user_loans">User Loans</a>
|
||||
<a href="/project_management">Project Management</a>
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
35
templates/history.html
Normal file
35
templates/history.html
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<h2>Loan History</h2>
|
||||
{% if loans %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tablet</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Borrower</th>
|
||||
<th>Loan Date</th>
|
||||
<th>Return Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for loan in loans %}
|
||||
<tr>
|
||||
<td>{{ loan.brand }} {{ loan.model }}</td>
|
||||
<td>{{ loan.serial_number }}</td>
|
||||
<td>{{ loan.name }}</td>
|
||||
<td>{{ loan.loan_date }}</td>
|
||||
<td>{{ loan.return_date or '-' }}</td>
|
||||
<td>{{ loan.status }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No loan history available.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
63
templates/index.html
Normal file
63
templates/index.html
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="section">
|
||||
<h2>Available Tablets</h2>
|
||||
{% if available_tablets %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Brand</th>
|
||||
<th>Model</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for tablet in available_tablets %}
|
||||
<tr>
|
||||
<td>{{ tablet.brand }}</td>
|
||||
<td>{{ tablet.model }}</td>
|
||||
<td>{{ tablet.serial_number }}</td>
|
||||
<td>{{ tablet.notes or '-' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No available tablets.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Active Loans</h2>
|
||||
{% if active_loans %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tablet</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Borrower</th>
|
||||
<th>Loan Date</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for loan in active_loans %}
|
||||
<tr>
|
||||
<td>{{ loan.brand }} {{ loan.model }}</td>
|
||||
<td>{{ loan.serial_number }}</td>
|
||||
<td>{{ loan.name }}</td>
|
||||
<td>{{ loan.loan_date }}</td>
|
||||
<td>
|
||||
<a href="/return_tablet/{{ loan.id }}" class="btn btn-danger">Return</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No active loans.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
78
templates/loan_tablet.html
Normal file
78
templates/loan_tablet.html
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Loan Tablet</h2>
|
||||
<form method="POST" action="/loan_tablet">
|
||||
<!-- Tablet Selection with Search -->
|
||||
<div class="form-group">
|
||||
<label for="tablet_id">Tablet:</label>
|
||||
<input type="text" id="tablet_search" class="search-input"
|
||||
placeholder="Search tablets (brand, model, or serial)..."
|
||||
onkeyup="filterDropdown('tablet_search', 'tablet_id')">
|
||||
<select id="tablet_id" name="tablet_id" required size="5">
|
||||
<option value="">Select a tablet</option>
|
||||
{% for tablet in available_tablets %}
|
||||
<option value="{{ tablet.id }}"
|
||||
data-search="{{ tablet.brand|lower }} {{ tablet.model|lower }} {{ tablet.serial_number|lower }}">
|
||||
{{ tablet.brand }} {{ tablet.model }} ({{ tablet.serial_number }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- User Selection with Search -->
|
||||
<div class="form-group">
|
||||
<label for="user_id">User:</label>
|
||||
<input type="text" id="user_search" class="search-input"
|
||||
placeholder="Search users (name or ID)..."
|
||||
onkeyup="filterDropdown('user_search', 'user_id')">
|
||||
<select id="user_id" name="user_id" required size="5">
|
||||
<option value="">Select a user</option>
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}"
|
||||
data-search="{{ user.name|lower }} {{ user.identification|lower }}">
|
||||
{{ user.name }} ({{ user.identification }})
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn">Loan Tablet</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function filterDropdown(searchId, selectId) {
|
||||
const search = document.getElementById(searchId).value.toLowerCase();
|
||||
const select = document.getElementById(selectId);
|
||||
const options = select.options;
|
||||
|
||||
for (let i = 1; i < options.length; i++) {
|
||||
const searchText = options[i].getAttribute('data-search') || '';
|
||||
options[i].style.display = searchText.includes(search) ? '' : 'none';
|
||||
}
|
||||
|
||||
// Show first visible option if search is empty, otherwise keep selection
|
||||
if (search === '') {
|
||||
select.selectedIndex = 0;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
select[size] {
|
||||
width: 100%;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
196
templates/project_management.html
Normal file
196
templates/project_management.html
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Project Management - Development Notes</h2>
|
||||
|
||||
<div class="project-management-container">
|
||||
<div class="editor-section">
|
||||
<div class="editor-controls">
|
||||
<button onclick="saveNotes()" class="btn">Save Notes</button>
|
||||
<button onclick="loadNotes()" class="btn">Reload</button>
|
||||
<span id="status" style="margin-left: 15px; font-style: italic;"></span>
|
||||
</div>
|
||||
|
||||
<div class="editor-row">
|
||||
<div class="editor-col">
|
||||
<h3>Editor</h3>
|
||||
<textarea id="markdown-editor" class="markdown-editor"
|
||||
placeholder="Write your markdown notes here..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="editor-col">
|
||||
<h3>Preview</h3>
|
||||
<div id="markdown-preview" class="markdown-preview"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script>
|
||||
const NOTES_FILE = 'notes_development/project_notes.md';
|
||||
|
||||
// Initialize: load and render notes
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadNotes();
|
||||
|
||||
// Set up live preview
|
||||
const editor = document.getElementById('markdown-editor');
|
||||
const preview = document.getElementById('markdown-preview');
|
||||
|
||||
editor.addEventListener('input', function() {
|
||||
preview.innerHTML = marked.parse(editor.value);
|
||||
});
|
||||
});
|
||||
|
||||
function loadNotes() {
|
||||
fetch('/get_notes/' + NOTES_FILE)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('File not found, using default');
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(content => {
|
||||
document.getElementById('markdown-editor').value = content;
|
||||
document.getElementById('markdown-preview').innerHTML = marked.parse(content);
|
||||
document.getElementById('status').textContent = 'Loaded successfully';
|
||||
setTimeout(() => {
|
||||
document.getElementById('status').textContent = '';
|
||||
}, 2000);
|
||||
})
|
||||
.catch(error => {
|
||||
document.getElementById('status').textContent = 'Using default content';
|
||||
setTimeout(() => {
|
||||
document.getElementById('status').textContent = '';
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
function saveNotes() {
|
||||
const content = document.getElementById('markdown-editor').value;
|
||||
const statusEl = document.getElementById('status');
|
||||
|
||||
fetch('/save_notes', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ content: content, filename: NOTES_FILE })
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
statusEl.textContent = 'Saved successfully!';
|
||||
statusEl.style.color = '#4CAF50';
|
||||
} else {
|
||||
statusEl.textContent = 'Error saving';
|
||||
statusEl.style.color = '#f44336';
|
||||
}
|
||||
setTimeout(() => {
|
||||
statusEl.textContent = '';
|
||||
statusEl.style.color = '';
|
||||
}, 2000);
|
||||
})
|
||||
.catch(error => {
|
||||
statusEl.textContent = 'Error: ' + error.message;
|
||||
statusEl.style.color = '#f44336';
|
||||
setTimeout(() => {
|
||||
statusEl.textContent = '';
|
||||
statusEl.style.color = '';
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-save every 30 seconds
|
||||
setInterval(() => {
|
||||
saveNotes();
|
||||
}, 30000);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.project-management-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.editor-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.editor-row {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.editor-col {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.markdown-editor {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.markdown-preview {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: white;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.markdown-preview h1,
|
||||
.markdown-preview h2,
|
||||
.markdown-preview h3,
|
||||
.markdown-preview h4,
|
||||
.markdown-preview h5,
|
||||
.markdown-preview h6 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-preview pre {
|
||||
background-color: #f5f5f5;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.markdown-preview code {
|
||||
font-family: monospace;
|
||||
background-color: #f5f5f5;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown-preview table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.markdown-preview th,
|
||||
.markdown-preview td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.editor-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
157
templates/user_loans.html
Normal file
157
templates/user_loans.html
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>User Loans</h2>
|
||||
<p class="relationship-note">
|
||||
<strong>Relationship:</strong> One user can loan multiple tablets (one-to-many).
|
||||
This page demonstrates the many-to-many relationship between users and tablets via the loans table.
|
||||
</p>
|
||||
|
||||
<div class="user-loans-container">
|
||||
{% for user_data in users_with_loans %}
|
||||
{% set user = user_data.user %}
|
||||
{% set loans = user_data.loans %}
|
||||
|
||||
<div class="user-card">
|
||||
<div class="user-header">
|
||||
<h3>{{ user.name }} ({{ user.identification }})</h3>
|
||||
<span class="loan-count">{{ loans|length }} tablet{{ 's' if loans|length != 1 else '' }} loaned</span>
|
||||
</div>
|
||||
|
||||
{% if loans %}
|
||||
<table class="loans-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tablet</th>
|
||||
<th>Serial Number</th>
|
||||
<th>Loan Date</th>
|
||||
<th>Return Date</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for loan in loans %}
|
||||
<tr class="loan-row status-{{ loan.status }}">
|
||||
<td>{{ loan.brand }} {{ loan.model }}</td>
|
||||
<td>{{ loan.serial_number }}</td>
|
||||
<td>{{ loan.loan_date }}</td>
|
||||
<td>{{ loan.return_date or '-' }}</td>
|
||||
<td>
|
||||
<span class="status-badge status-{{ loan.status }}">
|
||||
{{ loan.status }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="no-loans">No loans recorded for this user.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% if users_with_loans|length == 0 %}
|
||||
<p class="no-data">No users found. Add users and loan tablets to see data here.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.relationship-note {
|
||||
background-color: #e8f5e9;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid #4CAF50;
|
||||
}
|
||||
|
||||
.user-loans-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.user-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.user-header h3 {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.loan-count {
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.loans-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.loans-table th,
|
||||
.loans-table td {
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.loans-table th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.loan-row.status-active {
|
||||
background-color: #fff8e1;
|
||||
}
|
||||
|
||||
.loan-row.status-returned {
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.status-badge.status-active {
|
||||
background-color: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.status-badge.status-returned {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.no-loans {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
210
test_app.py
Normal file
210
test_app.py
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the Tablet Management System
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
|
||||
def test_tablet_management():
|
||||
"""Test the tablet management system functionality"""
|
||||
|
||||
# Initialize database
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Create tables
|
||||
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'
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
identification TEXT UNIQUE
|
||||
)
|
||||
''')
|
||||
|
||||
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()
|
||||
|
||||
print("=== Testing Tablet Management System ===")
|
||||
|
||||
# Test 1: Add tablets
|
||||
print("\n1. Adding tablets...")
|
||||
tablets = [
|
||||
("Samsung", "Galaxy Tab S7", "SN001"),
|
||||
("Apple", "iPad Pro", "SN002"),
|
||||
("Lenovo", "Tab P11", "SN003")
|
||||
]
|
||||
|
||||
for brand, model, serial in tablets:
|
||||
try:
|
||||
cursor.execute('''
|
||||
INSERT INTO tablets (brand, model, serial_number, status)
|
||||
VALUES (?, ?, ?, 'available')
|
||||
''', (brand, model, serial))
|
||||
print(f" ✓ Added: {brand} {model} ({serial})")
|
||||
except sqlite3.IntegrityError:
|
||||
print(f" ✗ Duplicate: {serial}")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Test 2: Add users
|
||||
print("\n2. Adding users...")
|
||||
users = [
|
||||
("John Doe", "ID001"),
|
||||
("Jane Smith", "ID002"),
|
||||
("Bob Johnson", "ID003")
|
||||
]
|
||||
|
||||
for name, identification in users:
|
||||
try:
|
||||
cursor.execute('''
|
||||
INSERT INTO users (name, identification)
|
||||
VALUES (?, ?)
|
||||
''', (name, identification))
|
||||
print(f" ✓ Added: {name} ({identification})")
|
||||
except sqlite3.IntegrityError:
|
||||
print(f" ✗ Duplicate: {identification}")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Test 3: Show available tablets
|
||||
print("\n3. Available tablets:")
|
||||
cursor.execute("SELECT id, brand, model, serial_number FROM tablets WHERE status = 'available'")
|
||||
available_tablets = cursor.fetchall()
|
||||
|
||||
for tablet in available_tablets:
|
||||
print(f" ID {tablet[0]}: {tablet[1]} {tablet[2]} ({tablet[3]})")
|
||||
|
||||
# Test 4: Loan tablets
|
||||
print("\n4. Loaning tablets...")
|
||||
|
||||
# Loan tablet 1 to user 1
|
||||
tablet_id = 1
|
||||
user_id = 1
|
||||
|
||||
cursor.execute("UPDATE tablets SET status = 'loaned' WHERE id = ?", (tablet_id,))
|
||||
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))
|
||||
print(f" ✓ Loaned tablet {tablet_id} to user {user_id}")
|
||||
|
||||
# Loan tablet 2 to user 2
|
||||
tablet_id = 2
|
||||
user_id = 2
|
||||
|
||||
cursor.execute("UPDATE tablets SET status = 'loaned' WHERE id = ?", (tablet_id,))
|
||||
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))
|
||||
print(f" ✓ Loaned tablet {tablet_id} to user {user_id}")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Test 5: Show active loans
|
||||
print("\n5. 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()
|
||||
|
||||
for loan in active_loans:
|
||||
print(f" Loan {loan[0]}: {loan[1]} {loan[2]} ({loan[3]}) to {loan[4]} on {loan[5]}")
|
||||
|
||||
# Test 6: Return a tablet
|
||||
print("\n6. Returning tablet...")
|
||||
loan_id = 1
|
||||
|
||||
cursor.execute("SELECT tablet_id FROM loans WHERE id = ?", (loan_id,))
|
||||
loan = cursor.fetchone()
|
||||
|
||||
if loan:
|
||||
tablet_id = loan[0]
|
||||
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))
|
||||
cursor.execute("UPDATE tablets SET status = 'available' WHERE id = ?", (tablet_id,))
|
||||
print(f" ✓ Returned tablet for loan {loan_id}")
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Test 7: Show loan history
|
||||
print("\n7. Complete loan history:")
|
||||
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()
|
||||
|
||||
for loan in loans:
|
||||
return_date = loan[6] or 'Not returned'
|
||||
print(f" Loan {loan[0]}: {loan[1]} {loan[2]} ({loan[3]}) to {loan[4]}")
|
||||
print(f" Loan: {loan[5]}, Return: {return_date}, Status: {loan[7]}")
|
||||
|
||||
# Test 8: Show final status
|
||||
print("\n8. Final status:")
|
||||
|
||||
# Available tablets
|
||||
cursor.execute("SELECT COUNT(*) FROM tablets WHERE status = 'available'")
|
||||
available_count = cursor.fetchone()[0]
|
||||
print(f" Available tablets: {available_count}")
|
||||
|
||||
# Loaned tablets
|
||||
cursor.execute("SELECT COUNT(*) FROM tablets WHERE status = 'loaned'")
|
||||
loaned_count = cursor.fetchone()[0]
|
||||
print(f" Loaned tablets: {loaned_count}")
|
||||
|
||||
# Active loans
|
||||
cursor.execute("SELECT COUNT(*) FROM loans WHERE status = 'active'")
|
||||
active_loans_count = cursor.fetchone()[0]
|
||||
print(f" Active loans: {active_loans_count}")
|
||||
|
||||
# Returned loans
|
||||
cursor.execute("SELECT COUNT(*) FROM loans WHERE status = 'returned'")
|
||||
returned_loans_count = cursor.fetchone()[0]
|
||||
print(f" Returned loans: {returned_loans_count}")
|
||||
|
||||
conn.close()
|
||||
|
||||
print("\n=== Test completed successfully! ===")
|
||||
print("\nYou can now run the interactive application:")
|
||||
print(" python3 minimal_app.py")
|
||||
print("\nOr use the database directly with SQLite:")
|
||||
print(" sqlite3 tablets.db")
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_tablet_management()
|
||||
182
working_server.py
Normal file
182
working_server.py
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Working web server for tablet management system
|
||||
"""
|
||||
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||
import sqlite3
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
import json
|
||||
|
||||
class TabletHandler(BaseHTTPRequestHandler):
|
||||
def _set_headers(self, status=200, content_type='text/html'):
|
||||
self.send_response(status)
|
||||
self.send_header('Content-type', content_type)
|
||||
self.end_headers()
|
||||
|
||||
def do_GET(self):
|
||||
parsed = urlparse(self.path)
|
||||
|
||||
if parsed.path == '/':
|
||||
self._serve_main_page()
|
||||
elif parsed.path == '/api/tablets':
|
||||
self._serve_tablets()
|
||||
elif parsed.path == '/api/users':
|
||||
self._serve_users()
|
||||
elif parsed.path == '/api/loans':
|
||||
self._serve_loans()
|
||||
else:
|
||||
self._set_headers(404)
|
||||
self.wfile.write(b'404 Not Found')
|
||||
|
||||
def _serve_main_page(self):
|
||||
html = '''<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Tablet Management System</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
h1 { color: #4CAF50; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
.section { margin-bottom: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }
|
||||
th { background-color: #4CAF50; color: white; }
|
||||
.btn { padding: 8px 16px; background-color: #4CAF50; color: white; text-decoration: none; border-radius: 4px; }
|
||||
.btn-danger { background-color: #f44336; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Tablet Management System</h1>
|
||||
<p>SQLite backend - Port 8000</p>
|
||||
|
||||
<div class="section">
|
||||
<h2>Available Tablets</h2>
|
||||
<div id="tablets"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Active Loans</h2>
|
||||
<div id="loans"></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>System Info</h2>
|
||||
<p>✓ Database: tablets.db</p>
|
||||
<p>✓ Status: Operational</p>
|
||||
<p>Port: 8000</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Load data from API
|
||||
fetch('/api/tablets')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
let html = '<table><thead><tr><th>ID</th><th>Brand</th><th>Model</th><th>Serial</th><th>Status</th></tr></thead><tbody>';
|
||||
data.forEach(tablet => {
|
||||
html += `<tr><td>${tablet.id}</td><td>${tablet.brand}</td><td>${tablet.model}</td><td>${tablet.serial_number}</td><td>${tablet.status}</td></tr>`;
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('tablets').innerHTML = html;
|
||||
});
|
||||
|
||||
fetch('/api/loans')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
let html = '<table><thead><tr><th>ID</th><th>Tablet</th><th>User</th><th>Loan Date</th><th>Status</th></tr></thead><tbody>';
|
||||
data.forEach(loan => {
|
||||
html += `<tr><td>${loan.id}</td><td>${loan.tablet}</td><td>${loan.user}</td><td>${loan.loan_date}</td><td>${loan.status}</td></tr>`;
|
||||
});
|
||||
html += '</tbody></table>';
|
||||
document.getElementById('loans').innerHTML = html;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>'''
|
||||
|
||||
self._set_headers()
|
||||
self.wfile.write(html.encode('utf-8'))
|
||||
|
||||
def _serve_tablets(self):
|
||||
try:
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM tablets')
|
||||
tablets = [dict(row) for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
|
||||
self._set_headers(content_type='application/json')
|
||||
self.wfile.write(json.dumps(tablets).encode('utf-8'))
|
||||
except Exception as e:
|
||||
self._set_headers(500)
|
||||
self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))
|
||||
|
||||
def _serve_users(self):
|
||||
try:
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
conn.row_factory = sqlite3.Row
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT * FROM users')
|
||||
users = [dict(row) for row in cursor.fetchall()]
|
||||
conn.close()
|
||||
|
||||
self._set_headers(content_type='application/json')
|
||||
self.wfile.write(json.dumps(users).encode('utf-8'))
|
||||
except Exception as e:
|
||||
self._set_headers(500)
|
||||
self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))
|
||||
|
||||
def _serve_loans(self):
|
||||
try:
|
||||
conn = sqlite3.connect('tablets.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
SELECT l.id, l.tablet_id, l.user_id, l.loan_date, l.return_date, l.status,
|
||||
t.brand || ' ' || t.model as tablet,
|
||||
u.name as user
|
||||
FROM loans l
|
||||
JOIN tablets t ON l.tablet_id = t.id
|
||||
JOIN users u ON l.user_id = u.id
|
||||
''')
|
||||
|
||||
loans = []
|
||||
for row in cursor.fetchall():
|
||||
loans.append({
|
||||
'id': row[0],
|
||||
'tablet_id': row[1],
|
||||
'user_id': row[2],
|
||||
'loan_date': row[3],
|
||||
'return_date': row[4],
|
||||
'status': row[5],
|
||||
'tablet': row[6],
|
||||
'user': row[7]
|
||||
})
|
||||
conn.close()
|
||||
|
||||
self._set_headers(content_type='application/json')
|
||||
self.wfile.write(json.dumps(loans).encode('utf-8'))
|
||||
except Exception as e:
|
||||
self._set_headers(500)
|
||||
self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))
|
||||
|
||||
def run_server():
|
||||
server_address = ('', 8000)
|
||||
httpd = HTTPServer(server_address, TabletHandler)
|
||||
|
||||
print("Tablet Management System - Web Interface")
|
||||
print("========================================")
|
||||
print("Server running on: http://localhost:8000")
|
||||
print("Database: tablets.db (SQLite)")
|
||||
print("Press Ctrl+C to stop the server")
|
||||
print()
|
||||
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nServer stopped")
|
||||
|
||||
if __name__ == '__main__':
|
||||
run_server()
|
||||
Loading…
Add table
Add a link
Reference in a new issue