first commit
This commit is contained in:
145
.gitignore
vendored
Normal file
145
.gitignore
vendored
Normal 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
189
README.md
Normal 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
286
app.py
Normal 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
5
requirements.txt
Normal 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
2
static/logos/.gitkeep
Normal 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
2
static/qr_codes/.gitkeep
Normal 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
668
templates/index.html
Normal 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
208
test_api.py
Normal 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}")
|
||||
Reference in New Issue
Block a user