first commit

This commit is contained in:
2025-07-15 13:25:34 +03:00
commit d30d065f44
8 changed files with 1505 additions and 0 deletions

145
.gitignore vendored Normal file
View File

@@ -0,0 +1,145 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# Generated QR codes and uploads
static/qr_codes/*.png
static/logos/*
!static/qr_codes/.gitkeep
!static/logos/.gitkeep
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db

189
README.md Normal file
View File

@@ -0,0 +1,189 @@
# QR Code Manager
A comprehensive Python web application for creating, customizing, and managing QR codes. This application provides functionality similar to popular QR code generation websites with additional features for local management.
## Features
### QR Code Types Supported
- **Text**: Plain text QR codes
- **URL/Website**: Direct links to websites
- **WiFi**: WiFi network connection details
- **Email**: Pre-filled email composition
- **Phone**: Direct phone number dialing
- **SMS**: Pre-filled SMS messages
- **vCard**: Digital contact cards
### Customization Options
- **Colors**: Customizable foreground and background colors
- **Styles**: Square, rounded, or circular module styles
- **Size**: Adjustable module size (5-15px per module)
- **Logo**: Option to add custom logos to QR codes
### Management Features
- **Preview**: Real-time preview of generated QR codes
- **Download**: Export QR codes as PNG images
- **History**: View and manage previously generated QR codes
- **Delete**: Remove unwanted QR codes
- **Copy**: Copy QR codes to clipboard
## Installation
1. **Clone or navigate to the project directory**:
```bash
cd /home/pi/Desktop/qr-code_manager
```
2. **Install dependencies**:
```bash
pip install -r requirements.txt
```
Or if using the virtual environment that was created:
```bash
/home/pi/Desktop/qr-code_manager/.venv/bin/pip install -r requirements.txt
```
## Usage
1. **Start the server**:
```bash
python app.py
```
Or with the virtual environment:
```bash
/home/pi/Desktop/qr-code_manager/.venv/bin/python app.py
```
2. **Access the application**:
Open your web browser and navigate to `http://localhost:5000`
3. **Create QR codes**:
- Select the type of QR code you want to create
- Fill in the required information
- Customize the appearance (colors, style, size)
- Click "Generate QR Code"
- Download or copy your QR code
## API Endpoints
The application provides a RESTful API for programmatic access:
### Generate QR Code
- **POST** `/api/generate`
- **Body**: JSON with QR code parameters
- **Response**: Generated QR code data and download URL
### Download QR Code
- **GET** `/api/download/<qr_id>`
- **Response**: PNG image file
### List QR Codes
- **GET** `/api/qr_codes`
- **Response**: JSON array of all generated QR codes
### Get QR Code Details
- **GET** `/api/qr_codes/<qr_id>`
- **Response**: JSON with QR code details
### Delete QR Code
- **DELETE** `/api/qr_codes/<qr_id>`
- **Response**: Success/error status
### Upload Logo
- **POST** `/api/upload_logo`
- **Body**: Multipart form with logo file
- **Response**: Logo path for use in QR generation
## Example API Usage
### Generate a URL QR Code
```bash
curl -X POST http://localhost:5000/api/generate \
-H "Content-Type: application/json" \
-d '{
"type": "url",
"content": "https://github.com",
"foreground_color": "#000000",
"background_color": "#FFFFFF",
"style": "square",
"size": 10
}'
```
### Generate a WiFi QR Code
```bash
curl -X POST http://localhost:5000/api/generate \
-H "Content-Type: application/json" \
-d '{
"type": "wifi",
"wifi": {
"ssid": "MyNetwork",
"password": "MyPassword",
"security": "WPA"
},
"foreground_color": "#0066cc",
"background_color": "#ffffff"
}'
```
## File Structure
```
qr-code_manager/
├── app.py # Main Flask application
├── requirements.txt # Python dependencies
├── README.md # This file
├── templates/
│ └── index.html # Web interface
├── static/
│ ├── qr_codes/ # Generated QR code images
│ └── logos/ # Uploaded logo files
└── .venv/ # Virtual environment (if created)
```
## Dependencies
- **Flask**: Web framework
- **qrcode[pil]**: QR code generation with PIL support
- **Pillow**: Image processing
- **flask-cors**: Cross-Origin Resource Sharing support
- **python-dotenv**: Environment variable management
## Security Considerations
- The application runs in debug mode by default - disable for production
- File uploads are stored locally - implement proper validation and storage limits
- QR codes are stored in memory - consider using a database for persistence
- The server accepts connections from all interfaces (0.0.0.0) - restrict for production
## Customization
### Adding New QR Code Types
1. Add the new type to the HTML select options
2. Create corresponding form fields in the HTML
3. Add processing logic in the `/api/generate` endpoint
4. Update the JavaScript to handle the new type
### Styling
The application uses embedded CSS for easy customization. Modify the `<style>` section in `templates/index.html` to change the appearance.
### Storage
Currently uses in-memory storage. To persist data:
1. Install a database library (SQLite, PostgreSQL, etc.)
2. Replace the `qr_codes_db` dictionary with database operations
3. Add database initialization code
## Troubleshooting
### Common Issues
1. **Port already in use**: Change the port in `app.py` or stop the conflicting service
2. **Permission errors**: Ensure the application has write permissions to the static directories
3. **Missing dependencies**: Reinstall requirements with `pip install -r requirements.txt`
### Debug Mode
The application runs in debug mode, which provides detailed error messages and auto-reload functionality. Disable for production by setting `debug=False` in the `app.run()` call.
## License
This project is open source. Feel free to modify and distribute according to your needs.

286
app.py Normal file
View File

@@ -0,0 +1,286 @@
import os
import io
import base64
import json
import uuid
from datetime import datetime
from PIL import Image, ImageDraw
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer, CircleModuleDrawer, SquareModuleDrawer
from qrcode.image.styles.colorfills import SolidFillColorMask
from flask import Flask, request, jsonify, send_file, render_template
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
# Configuration
UPLOAD_FOLDER = 'static/qr_codes'
LOGOS_FOLDER = 'static/logos'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(LOGOS_FOLDER, exist_ok=True)
# In-memory storage for QR codes (in production, use a database)
qr_codes_db = {}
class QRCodeGenerator:
def __init__(self):
self.default_settings = {
'size': 10,
'border': 4,
'error_correction': qrcode.constants.ERROR_CORRECT_M,
'foreground_color': '#000000',
'background_color': '#FFFFFF',
'style': 'square'
}
def generate_qr_code(self, data, settings=None):
"""Generate QR code with custom settings"""
if settings is None:
settings = self.default_settings.copy()
else:
merged_settings = self.default_settings.copy()
merged_settings.update(settings)
settings = merged_settings
# Create QR code instance
qr = qrcode.QRCode(
version=1,
error_correction=settings['error_correction'],
box_size=settings['size'],
border=settings['border'],
)
qr.add_data(data)
qr.make(fit=True)
# Choose module drawer based on style
module_drawer = None
if settings['style'] == 'rounded':
module_drawer = RoundedModuleDrawer()
elif settings['style'] == 'circle':
module_drawer = CircleModuleDrawer()
else:
module_drawer = SquareModuleDrawer()
# Create color mask
color_mask = SolidFillColorMask(
back_color=settings['background_color'],
front_color=settings['foreground_color']
)
# Generate the image
img = qr.make_image(
image_factory=StyledPilImage,
module_drawer=module_drawer,
color_mask=color_mask
)
return img
def add_logo(self, qr_img, logo_path, logo_size_ratio=0.2):
"""Add logo to QR code"""
try:
logo = Image.open(logo_path)
# Calculate logo size
qr_width, qr_height = qr_img.size
logo_size = int(min(qr_width, qr_height) * logo_size_ratio)
# Resize logo
logo = logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
# Create a white background for the logo
logo_bg = Image.new('RGB', (logo_size + 20, logo_size + 20), 'white')
logo_bg.paste(logo, (10, 10))
# Calculate position to center the logo
logo_pos = (
(qr_width - logo_bg.width) // 2,
(qr_height - logo_bg.height) // 2
)
# Paste logo onto QR code
qr_img.paste(logo_bg, logo_pos)
return qr_img
except Exception as e:
print(f"Error adding logo: {e}")
return qr_img
# Initialize QR code generator
qr_generator = QRCodeGenerator()
@app.route('/')
def index():
"""Serve the main page"""
return render_template('index.html')
@app.route('/api/generate', methods=['POST'])
def generate_qr():
"""Generate QR code API endpoint"""
try:
data = request.json
# Extract QR code content
qr_type = data.get('type', 'text')
content = data.get('content', '')
# Process content based on type
if qr_type == 'url':
qr_content = content if content.startswith(('http://', 'https://')) else f'https://{content}'
elif qr_type == 'wifi':
wifi_data = data.get('wifi', {})
qr_content = f"WIFI:T:{wifi_data.get('security', 'WPA')};S:{wifi_data.get('ssid', '')};P:{wifi_data.get('password', '')};H:{wifi_data.get('hidden', 'false')};;"
elif qr_type == 'email':
email_data = data.get('email', {})
qr_content = f"mailto:{email_data.get('email', '')}?subject={email_data.get('subject', '')}&body={email_data.get('body', '')}"
elif qr_type == 'phone':
qr_content = f"tel:{content}"
elif qr_type == 'sms':
sms_data = data.get('sms', {})
qr_content = f"smsto:{sms_data.get('phone', '')}:{sms_data.get('message', '')}"
elif qr_type == 'vcard':
vcard_data = data.get('vcard', {})
qr_content = f"""BEGIN:VCARD
VERSION:3.0
FN:{vcard_data.get('name', '')}
ORG:{vcard_data.get('organization', '')}
TEL:{vcard_data.get('phone', '')}
EMAIL:{vcard_data.get('email', '')}
URL:{vcard_data.get('website', '')}
END:VCARD"""
else: # text
qr_content = content
# Extract styling options
settings = {
'size': data.get('size', 10),
'border': data.get('border', 4),
'foreground_color': data.get('foreground_color', '#000000'),
'background_color': data.get('background_color', '#FFFFFF'),
'style': data.get('style', 'square')
}
# Generate QR code
qr_img = qr_generator.generate_qr_code(qr_content, settings)
# Add logo if provided
logo_path = data.get('logo_path')
if logo_path and os.path.exists(logo_path):
qr_img = qr_generator.add_logo(qr_img, logo_path)
# Convert to base64
img_buffer = io.BytesIO()
qr_img.save(img_buffer, format='PNG')
img_buffer.seek(0)
img_base64 = base64.b64encode(img_buffer.getvalue()).decode()
# Save QR code record
qr_id = str(uuid.uuid4())
qr_record = {
'id': qr_id,
'type': qr_type,
'content': qr_content,
'settings': settings,
'created_at': datetime.now().isoformat(),
'image_data': img_base64
}
qr_codes_db[qr_id] = qr_record
# Save image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
qr_img.save(img_path)
return jsonify({
'success': True,
'qr_id': qr_id,
'image_data': f'data:image/png;base64,{img_base64}',
'download_url': f'/api/download/{qr_id}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/download/<qr_id>')
def download_qr(qr_id):
"""Download QR code"""
try:
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
if os.path.exists(img_path):
return send_file(img_path, as_attachment=True, download_name=f'qr_code_{qr_id}.png')
else:
return jsonify({'error': 'QR code not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/qr_codes')
def list_qr_codes():
"""List all generated QR codes"""
qr_list = []
for qr_id, qr_data in qr_codes_db.items():
qr_list.append({
'id': qr_id,
'type': qr_data['type'],
'created_at': qr_data['created_at'],
'preview': f'data:image/png;base64,{qr_data["image_data"]}'
})
return jsonify(qr_list)
@app.route('/api/qr_codes/<qr_id>')
def get_qr_code(qr_id):
"""Get specific QR code details"""
if qr_id in qr_codes_db:
return jsonify(qr_codes_db[qr_id])
else:
return jsonify({'error': 'QR code not found'}), 404
@app.route('/api/qr_codes/<qr_id>', methods=['DELETE'])
def delete_qr_code(qr_id):
"""Delete QR code"""
try:
if qr_id in qr_codes_db:
# Remove from database
del qr_codes_db[qr_id]
# Remove image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
if os.path.exists(img_path):
os.remove(img_path)
return jsonify({'success': True})
else:
return jsonify({'error': 'QR code not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/upload_logo', methods=['POST'])
def upload_logo():
"""Upload logo for QR code"""
try:
if 'logo' not in request.files:
return jsonify({'error': 'No logo file provided'}), 400
file = request.files['logo']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# Save logo
logo_id = str(uuid.uuid4())
logo_extension = file.filename.rsplit('.', 1)[1].lower()
logo_filename = f'{logo_id}.{logo_extension}'
logo_path = os.path.join(LOGOS_FOLDER, logo_filename)
file.save(logo_path)
return jsonify({
'success': True,
'logo_path': logo_path,
'logo_id': logo_id
})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Flask==2.3.3
qrcode[pil]==7.4.2
Pillow==10.0.1
flask-cors==4.0.0
python-dotenv==1.0.0

2
static/logos/.gitkeep Normal file
View File

@@ -0,0 +1,2 @@
# This file ensures the directory is tracked by git
# Uploaded logo files will be stored here

2
static/qr_codes/.gitkeep Normal file
View File

@@ -0,0 +1,2 @@
# This file ensures the directory is tracked by git
# Generated QR code images will be stored here

668
templates/index.html Normal file
View File

@@ -0,0 +1,668 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QR Code Manager</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 15px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
.header p {
font-size: 1.1em;
opacity: 0.9;
}
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
padding: 30px;
}
.form-section {
background: #f8f9fa;
padding: 25px;
border-radius: 10px;
border: 1px solid #e9ecef;
}
.form-section h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5em;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #555;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
transition: border-color 0.3s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #667eea;
}
.form-group textarea {
height: 100px;
resize: vertical;
}
.color-inputs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.color-input-group {
display: flex;
align-items: center;
gap: 10px;
}
.color-input-group input[type="color"] {
width: 50px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
}
.style-selector {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.style-option {
padding: 10px;
border: 2px solid #e9ecef;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
background: white;
}
.style-option:hover {
border-color: #667eea;
}
.style-option.active {
border-color: #667eea;
background: #667eea;
color: white;
}
.wifi-fields,
.email-fields,
.sms-fields,
.vcard-fields {
display: none;
}
.wifi-fields.active,
.email-fields.active,
.sms-fields.active,
.vcard-fields.active {
display: block;
}
.btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px 30px;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
transition: transform 0.2s;
width: 100%;
margin-top: 10px;
}
.btn:hover {
transform: translateY(-2px);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.result-section {
text-align: center;
}
.qr-preview {
background: white;
border: 2px dashed #ddd;
border-radius: 10px;
padding: 30px;
margin-bottom: 20px;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.qr-preview img {
max-width: 100%;
max-height: 250px;
border-radius: 5px;
}
.qr-preview .placeholder {
color: #999;
font-size: 1.1em;
}
.download-section {
display: none;
gap: 10px;
}
.download-section.active {
display: flex;
}
.btn-secondary {
background: #6c757d;
flex: 1;
}
.btn-primary {
background: #28a745;
flex: 1;
}
.qr-history {
margin-top: 30px;
padding: 25px;
background: #f8f9fa;
border-radius: 10px;
border: 1px solid #e9ecef;
}
.qr-history h3 {
margin-bottom: 20px;
color: #333;
}
.qr-item {
display: flex;
align-items: center;
gap: 15px;
padding: 15px;
background: white;
border-radius: 8px;
margin-bottom: 10px;
border: 1px solid #e9ecef;
}
.qr-item img {
width: 50px;
height: 50px;
border-radius: 5px;
}
.qr-item-info {
flex: 1;
}
.qr-item-info h4 {
color: #333;
margin-bottom: 5px;
}
.qr-item-info p {
color: #666;
font-size: 0.9em;
}
.qr-item-actions {
display: flex;
gap: 10px;
}
.btn-small {
padding: 5px 15px;
font-size: 12px;
border-radius: 5px;
}
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
}
.color-inputs {
grid-template-columns: 1fr;
}
.style-selector {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎯 QR Code Manager</h1>
<p>Create, customize, and manage your QR codes with ease</p>
</div>
<div class="main-content">
<div class="form-section">
<h2>Generate QR Code</h2>
<div class="form-group">
<label for="qr-type">QR Code Type</label>
<select id="qr-type" onchange="toggleFields()">
<option value="text">Text</option>
<option value="url">URL/Website</option>
<option value="wifi">WiFi</option>
<option value="email">Email</option>
<option value="phone">Phone</option>
<option value="sms">SMS</option>
<option value="vcard">Contact Card</option>
</select>
</div>
<!-- Text/URL content -->
<div class="form-group" id="text-field">
<label for="content">Content</label>
<textarea id="content" placeholder="Enter your text or URL..."></textarea>
</div>
<!-- WiFi fields -->
<div class="wifi-fields" id="wifi-fields">
<div class="form-group">
<label for="wifi-ssid">Network Name (SSID)</label>
<input type="text" id="wifi-ssid" placeholder="My WiFi Network">
</div>
<div class="form-group">
<label for="wifi-password">Password</label>
<input type="password" id="wifi-password" placeholder="WiFi Password">
</div>
<div class="form-group">
<label for="wifi-security">Security Type</label>
<select id="wifi-security">
<option value="WPA">WPA/WPA2</option>
<option value="WEP">WEP</option>
<option value="nopass">No Password</option>
</select>
</div>
</div>
<!-- Email fields -->
<div class="email-fields" id="email-fields">
<div class="form-group">
<label for="email-address">Email Address</label>
<input type="email" id="email-address" placeholder="contact@example.com">
</div>
<div class="form-group">
<label for="email-subject">Subject</label>
<input type="text" id="email-subject" placeholder="Email subject">
</div>
<div class="form-group">
<label for="email-body">Message</label>
<textarea id="email-body" placeholder="Email message..."></textarea>
</div>
</div>
<!-- SMS fields -->
<div class="sms-fields" id="sms-fields">
<div class="form-group">
<label for="sms-phone">Phone Number</label>
<input type="tel" id="sms-phone" placeholder="+1234567890">
</div>
<div class="form-group">
<label for="sms-message">Message</label>
<textarea id="sms-message" placeholder="SMS message..."></textarea>
</div>
</div>
<!-- vCard fields -->
<div class="vcard-fields" id="vcard-fields">
<div class="form-group">
<label for="vcard-name">Full Name</label>
<input type="text" id="vcard-name" placeholder="John Doe">
</div>
<div class="form-group">
<label for="vcard-organization">Organization</label>
<input type="text" id="vcard-organization" placeholder="Company Name">
</div>
<div class="form-group">
<label for="vcard-phone">Phone</label>
<input type="tel" id="vcard-phone" placeholder="+1234567890">
</div>
<div class="form-group">
<label for="vcard-email">Email</label>
<input type="email" id="vcard-email" placeholder="john@example.com">
</div>
<div class="form-group">
<label for="vcard-website">Website</label>
<input type="url" id="vcard-website" placeholder="https://example.com">
</div>
</div>
<h3 style="margin: 25px 0 15px 0; color: #333;">Customization</h3>
<div class="form-group">
<label>Colors</label>
<div class="color-inputs">
<div class="color-input-group">
<input type="color" id="foreground-color" value="#000000">
<span>Foreground</span>
</div>
<div class="color-input-group">
<input type="color" id="background-color" value="#FFFFFF">
<span>Background</span>
</div>
</div>
</div>
<div class="form-group">
<label>Style</label>
<div class="style-selector">
<div class="style-option active" data-style="square">
<div></div>
<div>Square</div>
</div>
<div class="style-option" data-style="rounded">
<div></div>
<div>Rounded</div>
</div>
<div class="style-option" data-style="circle">
<div></div>
<div>Circle</div>
</div>
</div>
</div>
<div class="form-group">
<label for="size">Size</label>
<input type="range" id="size" min="5" max="15" value="10" oninput="updateSizeLabel()">
<small id="size-label">10px per module</small>
</div>
<button class="btn" onclick="generateQR()">Generate QR Code</button>
</div>
<div class="result-section">
<h2>Preview</h2>
<div class="qr-preview" id="qr-preview">
<div class="placeholder">
<div style="font-size: 3em; margin-bottom: 10px;">📱</div>
<div>Your QR code will appear here</div>
</div>
</div>
<div class="download-section" id="download-section">
<button class="btn btn-primary" onclick="downloadQR()">Download PNG</button>
<button class="btn btn-secondary" onclick="copyToClipboard()">Copy Image</button>
</div>
</div>
</div>
<div class="qr-history">
<h3>Recent QR Codes</h3>
<div id="qr-history-list">
<p style="color: #666; text-align: center;">No QR codes generated yet</p>
</div>
</div>
</div>
<script>
let currentQRId = null;
let currentQRData = null;
function toggleFields() {
const type = document.getElementById('qr-type').value;
// Hide all specific fields
document.getElementById('text-field').style.display = 'none';
document.querySelectorAll('.wifi-fields, .email-fields, .sms-fields, .vcard-fields').forEach(el => {
el.classList.remove('active');
});
// Show relevant fields
if (type === 'text' || type === 'url' || type === 'phone') {
document.getElementById('text-field').style.display = 'block';
const contentField = document.getElementById('content');
if (type === 'url') {
contentField.placeholder = 'https://example.com';
} else if (type === 'phone') {
contentField.placeholder = '+1234567890';
} else {
contentField.placeholder = 'Enter your text...';
}
} else {
document.getElementById(`${type}-fields`).classList.add('active');
}
}
function updateSizeLabel() {
const size = document.getElementById('size').value;
document.getElementById('size-label').textContent = `${size}px per module`;
}
// Style selector
document.querySelectorAll('.style-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.style-option').forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
});
});
async function generateQR() {
const type = document.getElementById('qr-type').value;
let content = '';
let additionalData = {};
// Get content based on type
if (type === 'text' || type === 'url' || type === 'phone') {
content = document.getElementById('content').value;
} else if (type === 'wifi') {
additionalData.wifi = {
ssid: document.getElementById('wifi-ssid').value,
password: document.getElementById('wifi-password').value,
security: document.getElementById('wifi-security').value
};
} else if (type === 'email') {
additionalData.email = {
email: document.getElementById('email-address').value,
subject: document.getElementById('email-subject').value,
body: document.getElementById('email-body').value
};
} else if (type === 'sms') {
additionalData.sms = {
phone: document.getElementById('sms-phone').value,
message: document.getElementById('sms-message').value
};
} else if (type === 'vcard') {
additionalData.vcard = {
name: document.getElementById('vcard-name').value,
organization: document.getElementById('vcard-organization').value,
phone: document.getElementById('vcard-phone').value,
email: document.getElementById('vcard-email').value,
website: document.getElementById('vcard-website').value
};
}
const requestData = {
type: type,
content: content,
...additionalData,
size: parseInt(document.getElementById('size').value),
foreground_color: document.getElementById('foreground-color').value,
background_color: document.getElementById('background-color').value,
style: document.querySelector('.style-option.active').dataset.style
};
try {
const response = await fetch('/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
});
const result = await response.json();
if (result.success) {
currentQRId = result.qr_id;
currentQRData = result.image_data;
// Show QR code
const preview = document.getElementById('qr-preview');
preview.innerHTML = `<img src="${result.image_data}" alt="Generated QR Code">`;
// Show download buttons
document.getElementById('download-section').classList.add('active');
// Refresh history
loadQRHistory();
} else {
alert('Error generating QR code: ' + result.error);
}
} catch (error) {
alert('Error: ' + error.message);
}
}
async function downloadQR() {
if (currentQRId) {
window.open(`/api/download/${currentQRId}`, '_blank');
}
}
async function copyToClipboard() {
if (currentQRData) {
try {
const response = await fetch(currentQRData);
const blob = await response.blob();
await navigator.clipboard.write([
new ClipboardItem({ [blob.type]: blob })
]);
alert('QR code copied to clipboard!');
} catch (error) {
alert('Failed to copy to clipboard');
}
}
}
async function loadQRHistory() {
try {
const response = await fetch('/api/qr_codes');
const qrCodes = await response.json();
const historyList = document.getElementById('qr-history-list');
if (qrCodes.length === 0) {
historyList.innerHTML = '<p style="color: #666; text-align: center;">No QR codes generated yet</p>';
return;
}
historyList.innerHTML = qrCodes.map(qr => `
<div class="qr-item">
<img src="${qr.preview}" alt="QR Code">
<div class="qr-item-info">
<h4>${qr.type.toUpperCase()}</h4>
<p>Created: ${new Date(qr.created_at).toLocaleDateString()}</p>
</div>
<div class="qr-item-actions">
<button class="btn btn-small btn-primary" onclick="downloadQRById('${qr.id}')">Download</button>
<button class="btn btn-small btn-secondary" onclick="deleteQR('${qr.id}')">Delete</button>
</div>
</div>
`).join('');
} catch (error) {
console.error('Failed to load QR history:', error);
}
}
async function downloadQRById(qrId) {
window.open(`/api/download/${qrId}`, '_blank');
}
async function deleteQR(qrId) {
if (confirm('Are you sure you want to delete this QR code?')) {
try {
const response = await fetch(`/api/qr_codes/${qrId}`, {
method: 'DELETE'
});
if (response.ok) {
loadQRHistory();
} else {
alert('Failed to delete QR code');
}
} catch (error) {
alert('Error deleting QR code: ' + error.message);
}
}
}
// Load history on page load
document.addEventListener('DOMContentLoaded', function() {
loadQRHistory();
});
</script>
</body>
</html>

208
test_api.py Normal file
View File

@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
Test script for QR Code Manager API
Run this script while the server is running to test API functionality
"""
import requests
import json
import base64
from PIL import Image
import io
# Server URL
BASE_URL = "http://localhost:5000"
def test_text_qr():
"""Test generating a text QR code"""
print("Testing text QR code generation...")
data = {
"type": "text",
"content": "Hello, World! This is a test QR code.",
"foreground_color": "#000000",
"background_color": "#FFFFFF",
"style": "square",
"size": 10
}
response = requests.post(f"{BASE_URL}/api/generate", json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ Text QR generated successfully! ID: {result['qr_id']}")
return result['qr_id']
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
return None
def test_url_qr():
"""Test generating a URL QR code"""
print("Testing URL QR code generation...")
data = {
"type": "url",
"content": "https://github.com",
"foreground_color": "#0066cc",
"background_color": "#ffffff",
"style": "rounded",
"size": 12
}
response = requests.post(f"{BASE_URL}/api/generate", json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ URL QR generated successfully! ID: {result['qr_id']}")
return result['qr_id']
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
return None
def test_wifi_qr():
"""Test generating a WiFi QR code"""
print("Testing WiFi QR code generation...")
data = {
"type": "wifi",
"wifi": {
"ssid": "TestNetwork",
"password": "TestPassword123",
"security": "WPA"
},
"foreground_color": "#ff6600",
"background_color": "#ffffff",
"style": "circle",
"size": 8
}
response = requests.post(f"{BASE_URL}/api/generate", json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ WiFi QR generated successfully! ID: {result['qr_id']}")
return result['qr_id']
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
return None
def test_list_qr_codes():
"""Test listing all QR codes"""
print("Testing QR codes listing...")
response = requests.get(f"{BASE_URL}/api/qr_codes")
if response.status_code == 200:
qr_codes = response.json()
print(f"✅ Found {len(qr_codes)} QR codes")
for qr in qr_codes:
print(f" - {qr['id']}: {qr['type']} (created: {qr['created_at']})")
return qr_codes
else:
print(f"❌ HTTP Error: {response.status_code}")
return []
def test_download_qr(qr_id):
"""Test downloading a QR code"""
print(f"Testing QR code download for ID: {qr_id}")
response = requests.get(f"{BASE_URL}/api/download/{qr_id}")
if response.status_code == 200:
print(f"✅ QR code downloaded successfully! Size: {len(response.content)} bytes")
# Verify it's a valid PNG image
try:
image = Image.open(io.BytesIO(response.content))
print(f" Image format: {image.format}, Size: {image.size}")
except Exception as e:
print(f"❌ Invalid image data: {e}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def test_get_qr_details(qr_id):
"""Test getting QR code details"""
print(f"Testing QR code details for ID: {qr_id}")
response = requests.get(f"{BASE_URL}/api/qr_codes/{qr_id}")
if response.status_code == 200:
qr_data = response.json()
print(f"✅ QR code details retrieved!")
print(f" Type: {qr_data['type']}")
print(f" Created: {qr_data['created_at']}")
print(f" Settings: {qr_data['settings']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def test_delete_qr(qr_id):
"""Test deleting a QR code"""
print(f"Testing QR code deletion for ID: {qr_id}")
response = requests.delete(f"{BASE_URL}/api/qr_codes/{qr_id}")
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ QR code deleted successfully!")
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def main():
"""Run all tests"""
print("🚀 Starting QR Code Manager API Tests")
print("=" * 50)
# Test generating different types of QR codes
text_qr_id = test_text_qr()
print()
url_qr_id = test_url_qr()
print()
wifi_qr_id = test_wifi_qr()
print()
# Test listing QR codes
qr_codes = test_list_qr_codes()
print()
# Test operations on generated QR codes
if text_qr_id:
test_download_qr(text_qr_id)
print()
test_get_qr_details(text_qr_id)
print()
# Test deletion (only delete one to keep some for manual testing)
if wifi_qr_id:
test_delete_qr(wifi_qr_id)
print()
print("🏁 Tests completed!")
print("You can now open http://localhost:5000 in your browser to test the web interface.")
if __name__ == "__main__":
try:
main()
except requests.exceptions.ConnectionError:
print("❌ Cannot connect to the server. Make sure the QR Code Manager is running on localhost:5000")
print("Start the server with: python app.py")
except Exception as e:
print(f"❌ Unexpected error: {e}")