(Add) init

This commit is contained in:
2024-08-10 12:17:51 +07:00
parent f3986cf3bd
commit 83cce0ae16
12 changed files with 418 additions and 0 deletions
+3
View File
@@ -1,3 +1,6 @@
.idea
output.xlsx
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
+19
View File
@@ -1,2 +1,21 @@
# QR-code-attendance
Just a QR code attendance demo app for my sister's representation
## How to use?
1. Install python3:
Go to [python.org](https://www.python.org/downloads/) and download the latest version of python3.
2. Clone the repository
```bash
git clone https://github.com/tiennm99/qr-code-attendance.git
```
Or download the zip file and extract it.
3. Run `setup.bat` to install the required packages.
4. Run `run.bat` to start the server.
# Note
**These code for educational purposes only. It is not secure and not optimized for production use.**
_Good luck, sis!_
+193
View File
@@ -0,0 +1,193 @@
from threading import Timer
import psutil
import qrcode
from flask import Flask, render_template, request, jsonify, redirect, url_for, abort
import io
import base64
import pandas as pd
from datetime import datetime
import os
from openpyxl import load_workbook
from openpyxl.styles import Font, PatternFill
import webbrowser
from collections import deque
from functools import wraps
app = Flask(__name__)
PORT = 31009
INPUT_EXCEL = 'input.xlsx'
OUTPUT_EXCEL = 'output.xlsx'
# Use a deque to store the last submissions
last_submissions = deque(maxlen=10)
def open_browser():
webbrowser.open_new(f'http://localhost:{PORT}/private')
def get_ip_addresses():
ip_addresses = []
for interface, addrs in psutil.net_if_addrs().items():
for addr in addrs:
if addr.family == 2: # AF_INET (IPv4)
ip_addresses.append((interface, addr.address))
return ip_addresses
def generate_qr_code(data):
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
return img
def initialize_output_excel():
if not os.path.exists(INPUT_EXCEL):
raise FileNotFoundError(f"{INPUT_EXCEL} not found. Please provide the input Excel file.")
df = pd.read_excel(INPUT_EXCEL)
df['Attended'] = ''
df['Submit Time'] = ''
df['IP'] = ''
df['User Agent'] = ''
df.to_excel(OUTPUT_EXCEL, index=False)
def localhost_only(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if request.remote_addr != '127.0.0.1':
abort(403) # Forbidden
return f(*args, **kwargs)
return decorated_function
@app.route('/private')
@localhost_only
def index():
ip_addresses = get_ip_addresses()
return render_template('index.html', ip_addresses=ip_addresses, last_submissions=list(last_submissions))
@app.route('/private/generate_qr', methods=['POST'])
@localhost_only
def generate_qr():
selected_ip = request.json.get('ip')
full_url = f"http://{selected_ip}:{PORT}/public/attendance"
qr_img = generate_qr_code(full_url)
img_buffer = io.BytesIO()
qr_img.save(img_buffer, format='PNG')
img_buffer.seek(0)
qr_code = base64.b64encode(img_buffer.getvalue()).decode()
return jsonify({'qr_code': qr_code, 'full_url': full_url})
@app.route('/public/attendance', methods=['GET', 'POST'])
def attendance():
if request.method == 'POST':
student_id = request.form['student_id']
ip_address = request.remote_addr
user_agent = request.user_agent.string
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
result = update_attendance(student_id, timestamp, ip_address, user_agent)
if result == 'duplicate':
return redirect(url_for('attendance_error', error='duplicate'))
elif result == 'student_not_found':
return redirect(url_for('attendance_error', error='not_found'))
return redirect(url_for('attendance_success'))
return render_template('attendance_form.html')
@app.route('/attendance_success')
def attendance_success():
return render_template('attendance_success.html')
@app.route('/attendance_error')
def attendance_error():
error = request.args.get('error', 'unknown')
if error == 'duplicate':
message = "Your attendance has been recorded, but it appears to be a duplicate submission."
elif error == 'not_found':
message = "Student ID not found. Please check your ID and try again."
else:
message = "An unknown error occurred. Please try again later."
return render_template('attendance_error.html', message=message)
def update_attendance(student_id, timestamp, ip_address, user_agent):
wb = load_workbook(OUTPUT_EXCEL)
ws = wb.active
student_row = None
for row in ws.iter_rows(min_row=2, max_row=ws.max_row):
if str(row[0].value) == str(student_id):
student_row = row
break
if student_row is None:
wb.close()
return 'student_not_found'
row_index = student_row[0].row
is_duplicate = False
duplicate_with = None
# Check for duplicate IP or User Agent
for other_row in ws.iter_rows(min_row=2, max_row=ws.max_row, min_col=5, max_col=6):
if other_row[0].value == ip_address or other_row[1].value == user_agent:
is_duplicate = True
duplicate_with = ws.cell(row=other_row[0].row, column=1).value
break
# Check if this student has already attended
if ws.cell(row=row_index, column=3).value:
is_duplicate = True
# Always save submission details
ws.cell(row=row_index, column=4, value=timestamp)
ws.cell(row=row_index, column=5, value=ip_address)
ws.cell(row=row_index, column=6, value=user_agent)
if is_duplicate:
duplicate_cell = ws.cell(row=row_index, column=3)
if duplicate_with:
duplicate_cell.value = f"Duplicated with {duplicate_with}"
else:
duplicate_cell.value = "Duplicate submission"
duplicate_cell.font = Font(color="FF0000")
duplicate_cell.fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
else:
# Mark attendance
ws.cell(row=row_index, column=3, value='X')
# Add to last 5 submissions
student_name = ws.cell(row=row_index, column=2).value
last_submissions.appendleft({
'Student ID': student_id,
'Student Name': student_name,
'Submit Time': timestamp
})
wb.save(OUTPUT_EXCEL)
wb.close()
return 'duplicate' if is_duplicate else 'success'
@app.route('/private/get_last_submissions')
@localhost_only
def get_last_submissions():
return jsonify(list(last_submissions))
if __name__ == '__main__':
if not os.path.exists(OUTPUT_EXCEL):
initialize_output_excel()
Timer(1, open_browser).start()
app.run(host='0.0.0.0', port=PORT, debug=True)
BIN
View File
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
blinker==1.8.2
click==8.1.7
colorama==0.4.6
et-xmlfile==1.1.0
Flask==3.0.3
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
netifaces==0.11.0
numpy==2.0.1
openpyxl==3.1.5
pandas==2.2.2
pillow==10.4.0
psutil==6.0.0
pypng==0.20220715.0
python-dateutil==2.9.0.post0
pytz==2024.1
qrcode==7.4.2
six==1.16.0
typing_extensions==4.12.2
tzdata==2024.1
Werkzeug==3.0.3
+9
View File
@@ -0,0 +1,9 @@
@echo off
echo Starting the application...
python app.py
if %errorlevel% neq 0 (
echo Error: Failed to run the application.
pause
exit /b %errorlevel%
)
pause
+10
View File
@@ -0,0 +1,10 @@
@echo off
echo Installing requirements...
pip install -r requirements.txt
if %errorlevel% neq 0 (
echo Error: Failed to install requirements.
pause
exit /b %errorlevel%
)
echo Requirements installed successfully.
pause
+64
View File
@@ -0,0 +1,64 @@
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('ip-form');
const qrCodeContainer = document.getElementById('qr-code-container');
const submissionsTable = document.getElementById('submissions-table');
form.addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(form);
const selectedIp = formData.get('ip');
fetch('/private/generate_qr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ ip: selectedIp }),
})
.then(response => response.json())
.then(data => {
qrCodeContainer.innerHTML = `
<h2 class="mb-3">QR Code for ${data.full_url}</h2>
<img src="data:image/png;base64,${data.qr_code}" alt="QR Code" class="img-fluid">
`;
})
.catch(error => {
console.error('Error:', error);
qrCodeContainer.innerHTML = '<p class="text-danger">Error generating QR code. Please try again.</p>';
});
});
function updateSubmissions() {
fetch('/private/get_last_submissions')
.then(response => response.json())
.then(data => {
let tableHtml = `
<thead>
<tr>
<th>Student ID</th>
<th>Student Name</th>
<th>Submit Time</th>
</tr>
</thead>
<tbody>
`;
data.forEach(submission => {
tableHtml += `
<tr>
<td>${submission['Student ID']}</td>
<td>${submission['Student Name']}</td>
<td>${submission['Submit Time']}</td>
</tr>
`;
});
tableHtml += '</tbody>';
submissionsTable.innerHTML = tableHtml;
})
.catch(error => console.error('Error:', error));
}
// Update submissions every 5 seconds
setInterval(updateSubmissions, 5000);
// Initial update
updateSubmissions();
});
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Attendance Submission Error</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5 text-center">
<h1 class="mb-4">Attendance Submission Error</h1>
<p class="lead">{{ message }}</p>
<a href="{{ url_for('attendance') }}" class="btn btn-primary">Try Again</a>
</div>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Student Attendance Form</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center mb-4">Student Attendance Form</h1>
<form action="{{ url_for('attendance') }}" method="post">
<div class="mb-3">
<label for="student_id" class="form-label">Student ID</label>
<input type="text" class="form-control" id="student_id" name="student_id" required>
</div>
<button type="submit" class="btn btn-primary">Submit Attendance</button>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
+15
View File
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Attendance Submitted Successfully</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5 text-center">
<h1 class="mb-4">Attendance Submitted Successfully</h1>
<p class="lead">Thank you for submitting your attendance.</p>
</div>
</body>
</html>
+45
View File
@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QR Code Attendance</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="text-center mb-4">QR Code Attendance</h1>
<form id="ip-form">
<select class="form-select mb-3" id="ip-select" name="ip">
{% for interface, ip in ip_addresses %}
<option value="{{ ip }}">{{ interface }}: {{ ip }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary">Generate QR Code</button>
</form>
<div id="qr-code-container" class="mt-4 text-center"></div>
<h2 class="mt-5">Last Submissions</h2>
<table id="submissions-table" class="table">
<thead>
<tr>
<th>Student ID</th>
<th>Student Name</th>
<th>Submit Time</th>
</tr>
</thead>
<tbody>
{% for submission in last_submissions %}
<tr>
<td>{{ submission['Student ID'] }}</td>
<td>{{ submission['Student Name'] }}</td>
<td>{{ submission['Submit Time'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>