refactoring project
This commit is contained in:
@@ -283,3 +283,5 @@ def generate_barcode_page(product_id):
|
|||||||
product_id=product_id,
|
product_id=product_id,
|
||||||
product=product_data,
|
product=product_data,
|
||||||
dependencies=dependency_status)
|
dependencies=dependency_status)
|
||||||
|
|
||||||
|
# Remove or replace this file, as admin.py has been moved to routes/admin.py
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from flask import Flask, jsonify, request, send_file, render_template, flash
|
from flask import Flask, jsonify, request, send_file, render_template, flash
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
from admin import admin_bp
|
from routes.admin import admin_bp
|
||||||
|
from routes.api import api_bp
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
# Set DATA_FOLDER to the absolute path of the data directory inside src
|
# Set DATA_FOLDER to the absolute path of the data directory inside src
|
||||||
@@ -10,6 +11,7 @@ app.config['SECRET_KEY'] = 'dev-key-for-demo-only'
|
|||||||
|
|
||||||
# Register blueprints
|
# Register blueprints
|
||||||
app.register_blueprint(admin_bp)
|
app.register_blueprint(admin_bp)
|
||||||
|
app.register_blueprint(api_bp)
|
||||||
|
|
||||||
# Load product data from products.json
|
# Load product data from products.json
|
||||||
def load_products():
|
def load_products():
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
import os
|
|
||||||
import io
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
|
|
||||||
# Try to import barcode libraries, provide fallback if not available
|
|
||||||
try:
|
|
||||||
import qrcode
|
|
||||||
QRCODE_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
QRCODE_AVAILABLE = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
from barcode import EAN13, Code128
|
|
||||||
from barcode.writer import ImageWriter
|
|
||||||
BARCODE_AVAILABLE = True
|
|
||||||
except ImportError:
|
|
||||||
BARCODE_AVAILABLE = False
|
|
||||||
|
|
||||||
class BarcodeGenerator:
|
|
||||||
"""
|
|
||||||
A utility class for generating QR codes and barcodes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_dependencies():
|
|
||||||
"""
|
|
||||||
Check if all required dependencies are installed.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: Status of each dependency
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'qrcode': QRCODE_AVAILABLE,
|
|
||||||
'barcode': BARCODE_AVAILABLE,
|
|
||||||
'pillow': True # PIL/Pillow is imported directly at the top level
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def generate_qr_code(data, size=10, border=4):
|
|
||||||
"""
|
|
||||||
Generates a QR code as a PIL Image object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (str): The data to encode in the QR code
|
|
||||||
size (int): The size of the QR code (1-40)
|
|
||||||
border (int): The border size of the QR code
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PIL.Image: The generated QR code image or a placeholder image if qrcode is not available
|
|
||||||
"""
|
|
||||||
if not QRCODE_AVAILABLE:
|
|
||||||
return BarcodeGenerator._generate_placeholder("QR Code Unavailable\nPlease install 'qrcode' package")
|
|
||||||
|
|
||||||
qr = qrcode.QRCode(
|
|
||||||
version=size,
|
|
||||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
|
||||||
box_size=10,
|
|
||||||
border=border,
|
|
||||||
)
|
|
||||||
qr.add_data(data)
|
|
||||||
qr.make(fit=True)
|
|
||||||
|
|
||||||
img = qr.make_image(fill_color="black", back_color="white")
|
|
||||||
return img
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def generate_ean13_barcode(data):
|
|
||||||
"""
|
|
||||||
Generates an EAN-13 barcode as a PIL Image object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (str): The data to encode in the barcode (must be 12 digits)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PIL.Image: The generated barcode image or a placeholder image if barcode is not available
|
|
||||||
"""
|
|
||||||
if not BARCODE_AVAILABLE:
|
|
||||||
return BarcodeGenerator._generate_placeholder("EAN-13 Unavailable\nPlease install 'python-barcode' package")
|
|
||||||
|
|
||||||
# Pad data to 12 digits if needed
|
|
||||||
if len(data) < 12:
|
|
||||||
data = data.zfill(12)
|
|
||||||
elif len(data) > 12:
|
|
||||||
data = data[:12]
|
|
||||||
|
|
||||||
# Create a BytesIO object to temporarily store the image
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
EAN13(data, writer=ImageWriter()).write(buffer)
|
|
||||||
|
|
||||||
# Create a PIL Image from the BytesIO object
|
|
||||||
buffer.seek(0)
|
|
||||||
image = Image.open(buffer)
|
|
||||||
|
|
||||||
return image
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def generate_code128_barcode(data):
|
|
||||||
"""
|
|
||||||
Generates a Code 128 barcode as a PIL Image object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data (str): The data to encode in the barcode
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PIL.Image: The generated barcode image or a placeholder image if barcode is not available
|
|
||||||
"""
|
|
||||||
if not BARCODE_AVAILABLE:
|
|
||||||
return BarcodeGenerator._generate_placeholder("Code 128 Unavailable\nPlease install 'python-barcode' package")
|
|
||||||
|
|
||||||
# Create a BytesIO object to temporarily store the image
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
Code128(data, writer=ImageWriter()).write(buffer)
|
|
||||||
|
|
||||||
# Create a PIL Image from the BytesIO object
|
|
||||||
buffer.seek(0)
|
|
||||||
image = Image.open(buffer)
|
|
||||||
|
|
||||||
return image
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _generate_placeholder(message):
|
|
||||||
"""
|
|
||||||
Generates a placeholder image with a message.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
message (str): Message to display on the placeholder
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PIL.Image: The generated placeholder image
|
|
||||||
"""
|
|
||||||
# Create a placeholder image
|
|
||||||
img = Image.new('RGB', (300, 150), color=(255, 255, 255))
|
|
||||||
d = ImageDraw.Draw(img)
|
|
||||||
|
|
||||||
# Draw a border
|
|
||||||
d.rectangle([0, 0, 299, 149], outline=(0, 0, 0), width=2)
|
|
||||||
|
|
||||||
# Add text
|
|
||||||
try:
|
|
||||||
# Try to use a default font
|
|
||||||
font = ImageFont.truetype("arial.ttf", 20)
|
|
||||||
except IOError:
|
|
||||||
# Fallback to default font
|
|
||||||
font = ImageFont.load_default()
|
|
||||||
|
|
||||||
# Center the text
|
|
||||||
lines = message.split('\n')
|
|
||||||
y_offset = 40
|
|
||||||
for line in lines:
|
|
||||||
text_width = d.textlength(line, font=font)
|
|
||||||
d.text(((300 - text_width) // 2, y_offset), line, font=font, fill=(0, 0, 0))
|
|
||||||
y_offset += 30
|
|
||||||
|
|
||||||
return img
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def save_image(image, path):
|
|
||||||
"""
|
|
||||||
Saves a PIL Image to the specified path.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image (PIL.Image): The image to save
|
|
||||||
path (str): The path to save the image to
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: The path where the image was saved
|
|
||||||
"""
|
|
||||||
image.save(path)
|
|
||||||
return path
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_image_as_bytes(image, format='PNG'):
|
|
||||||
"""
|
|
||||||
Converts a PIL Image to bytes.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image (PIL.Image): The image to convert
|
|
||||||
format (str): The format to save the image as
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bytes: The image as bytes
|
|
||||||
"""
|
|
||||||
buffer = io.BytesIO()
|
|
||||||
image.save(buffer, format=format)
|
|
||||||
return buffer.getvalue()
|
|
||||||
1
src/routes/__init__.py
Normal file
1
src/routes/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# This file is intentionally left blank to make 'routes' a package.
|
||||||
284
src/routes/admin.py
Normal file
284
src/routes/admin.py
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify, send_file
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
import io
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
# Import barcode generator class, but handle the case if it fails
|
||||||
|
try:
|
||||||
|
from utils.barcode_generator import BarcodeGenerator
|
||||||
|
BARCODE_GENERATOR_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
BARCODE_GENERATOR_AVAILABLE = False
|
||||||
|
class BarcodeGenerator:
|
||||||
|
@staticmethod
|
||||||
|
def check_dependencies():
|
||||||
|
return {'qrcode': False, 'barcode': False, 'pillow': False}
|
||||||
|
|
||||||
|
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
|
||||||
|
|
||||||
|
DATA_FOLDER = os.path.join(os.path.dirname(__file__), '../data')
|
||||||
|
PRODUCTS_FILE = os.path.join(DATA_FOLDER, 'products.json')
|
||||||
|
UPLOAD_FOLDER = os.path.join(DATA_FOLDER, 'images')
|
||||||
|
BARCODE_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'data', 'barcodes'))
|
||||||
|
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
||||||
|
|
||||||
|
def allowed_file(filename):
|
||||||
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||||
|
|
||||||
|
def load_products():
|
||||||
|
try:
|
||||||
|
with open(PRODUCTS_FILE, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except (FileNotFoundError, json.JSONDecodeError):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def save_products(products):
|
||||||
|
with open(PRODUCTS_FILE, 'w') as f:
|
||||||
|
json.dump(products, f, indent=2)
|
||||||
|
|
||||||
|
@admin_bp.route('/')
|
||||||
|
def index():
|
||||||
|
products = load_products()
|
||||||
|
return render_template('admin/index.html', products=products)
|
||||||
|
|
||||||
|
@admin_bp.route('/add', methods=['GET', 'POST'])
|
||||||
|
def add_product():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Get form data
|
||||||
|
product_id = request.form.get('product_id')
|
||||||
|
name = request.form.get('name')
|
||||||
|
price = request.form.get('price')
|
||||||
|
|
||||||
|
# Basic validation
|
||||||
|
if not product_id or not name or not price:
|
||||||
|
flash('Product ID, Name, and Price are required fields.')
|
||||||
|
return render_template('admin/add_product.html')
|
||||||
|
|
||||||
|
# Check if product ID already exists
|
||||||
|
products = load_products()
|
||||||
|
if product_id in products:
|
||||||
|
flash('Product ID already exists.')
|
||||||
|
return render_template('admin/add_product.html')
|
||||||
|
|
||||||
|
# Handle image upload
|
||||||
|
image_filename = f"{product_id}.png" # Default name
|
||||||
|
image_path = f"/images/{image_filename}"
|
||||||
|
|
||||||
|
if 'image' in request.files:
|
||||||
|
file = request.files['image']
|
||||||
|
if file and file.filename and allowed_file(file.filename):
|
||||||
|
extension = file.filename.rsplit('.', 1)[1].lower()
|
||||||
|
image_filename = f"{product_id}.{extension}"
|
||||||
|
image_path = f"/images/{image_filename}"
|
||||||
|
file.save(os.path.join(UPLOAD_FOLDER, image_filename))
|
||||||
|
|
||||||
|
# Create product data structure
|
||||||
|
product_data = [
|
||||||
|
{"fieldName": "_id", "label": "Item ID", "value": product_id, "editable": "false", "fieldType": "TEXT"},
|
||||||
|
{"fieldName": "_name", "label": "Product Name", "value": name, "editable": "true", "fieldType": "TEXT"},
|
||||||
|
{"fieldName": "_price", "label": "Sale Price", "value": f"${price}", "editable": "true", "fieldType": "TEXT"},
|
||||||
|
{"fieldName": "_image", "label": "Image", "value": image_path, "editable": "false", "fieldType": "IMAGE_URI"}
|
||||||
|
]
|
||||||
|
|
||||||
|
# Save to products.json
|
||||||
|
products[product_id] = product_data
|
||||||
|
save_products(products)
|
||||||
|
|
||||||
|
flash('Product added successfully!')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
return render_template('admin/add_product.html')
|
||||||
|
|
||||||
|
@admin_bp.route('/edit/<product_id>', methods=['GET', 'POST'])
|
||||||
|
def edit_product(product_id):
|
||||||
|
products = load_products()
|
||||||
|
|
||||||
|
if product_id not in products:
|
||||||
|
flash('Product not found.')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Get form data
|
||||||
|
name = request.form.get('name')
|
||||||
|
price = request.form.get('price')
|
||||||
|
|
||||||
|
# Basic validation
|
||||||
|
if not name or not price:
|
||||||
|
flash('Name and Price are required fields.')
|
||||||
|
return render_template('admin/edit_product.html', product_id=product_id, product=products[product_id])
|
||||||
|
|
||||||
|
# Update name and price
|
||||||
|
for field in products[product_id]:
|
||||||
|
if field["fieldName"] == "_name":
|
||||||
|
field["value"] = name
|
||||||
|
elif field["fieldName"] == "_price":
|
||||||
|
field["value"] = f"${price}"
|
||||||
|
|
||||||
|
# Handle image upload
|
||||||
|
if 'image' in request.files and request.files['image'].filename:
|
||||||
|
file = request.files['image']
|
||||||
|
if allowed_file(file.filename):
|
||||||
|
# Find current image path
|
||||||
|
current_image = None
|
||||||
|
for field in products[product_id]:
|
||||||
|
if field["fieldName"] == "_image":
|
||||||
|
current_image = field["value"]
|
||||||
|
|
||||||
|
if current_image:
|
||||||
|
# Get current filename
|
||||||
|
current_filename = os.path.basename(current_image)
|
||||||
|
# Delete old file if it exists and is different
|
||||||
|
old_path = os.path.join(UPLOAD_FOLDER, current_filename)
|
||||||
|
if os.path.exists(old_path):
|
||||||
|
os.remove(old_path)
|
||||||
|
|
||||||
|
# Save new image
|
||||||
|
extension = file.filename.rsplit('.', 1)[1].lower()
|
||||||
|
image_filename = f"{product_id}.{extension}"
|
||||||
|
image_path = f"/images/{image_filename}"
|
||||||
|
file.save(os.path.join(UPLOAD_FOLDER, image_filename))
|
||||||
|
|
||||||
|
# Update image path
|
||||||
|
for field in products[product_id]:
|
||||||
|
if field["fieldName"] == "_image":
|
||||||
|
field["value"] = image_path
|
||||||
|
|
||||||
|
# Save to products.json
|
||||||
|
save_products(products)
|
||||||
|
|
||||||
|
flash('Product updated successfully!')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
return render_template('admin/edit_product.html', product_id=product_id, product=products[product_id])
|
||||||
|
|
||||||
|
@admin_bp.route('/delete/<product_id>', methods=['POST'])
|
||||||
|
def delete_product(product_id):
|
||||||
|
products = load_products()
|
||||||
|
|
||||||
|
if product_id not in products:
|
||||||
|
flash('Product not found.')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
# Find image path
|
||||||
|
image_path = None
|
||||||
|
for field in products[product_id]:
|
||||||
|
if field["fieldName"] == "_image":
|
||||||
|
image_path = field["value"]
|
||||||
|
|
||||||
|
# Delete image file
|
||||||
|
if image_path:
|
||||||
|
image_filename = os.path.basename(image_path)
|
||||||
|
image_file_path = os.path.join(UPLOAD_FOLDER, image_filename)
|
||||||
|
if os.path.exists(image_file_path):
|
||||||
|
os.remove(image_file_path)
|
||||||
|
|
||||||
|
# Delete product from dict
|
||||||
|
del products[product_id]
|
||||||
|
|
||||||
|
# Save updated products
|
||||||
|
save_products(products)
|
||||||
|
|
||||||
|
flash('Product deleted successfully!')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
@admin_bp.route('/view/<product_id>')
|
||||||
|
def view_product(product_id):
|
||||||
|
products = load_products()
|
||||||
|
|
||||||
|
if product_id not in products:
|
||||||
|
flash('Product not found.')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
return render_template('admin/view_product.html', product_id=product_id, product=products[product_id])
|
||||||
|
|
||||||
|
@admin_bp.route('/generate_barcode/<product_id>/<code_type>')
|
||||||
|
def generate_barcode(product_id, code_type):
|
||||||
|
"""Generate and serve a barcode or QR code for a product"""
|
||||||
|
# Make sure the barcodes directory exists
|
||||||
|
os.makedirs(BARCODE_FOLDER, exist_ok=True)
|
||||||
|
|
||||||
|
products = load_products()
|
||||||
|
if product_id not in products:
|
||||||
|
return jsonify({"error": "Product not found"}), 404
|
||||||
|
|
||||||
|
# Check if barcode generator is available
|
||||||
|
if not BARCODE_GENERATOR_AVAILABLE:
|
||||||
|
# Create a simple error image
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
img = Image.new('RGB', (300, 150), color=(255, 255, 255))
|
||||||
|
d = ImageDraw.Draw(img)
|
||||||
|
d.rectangle([0, 0, 299, 149], outline=(0, 0, 0), width=2)
|
||||||
|
d.text((25, 65), "Barcode generation unavailable", fill=(0, 0, 0))
|
||||||
|
|
||||||
|
# Save and serve the error image
|
||||||
|
image_path = os.path.join(BARCODE_FOLDER, f"{product_id}_{code_type}_error.png")
|
||||||
|
img.save(image_path)
|
||||||
|
return send_file(image_path, mimetype='image/png')
|
||||||
|
|
||||||
|
generator = BarcodeGenerator()
|
||||||
|
|
||||||
|
# Content for the code - use the product ID
|
||||||
|
data = product_id
|
||||||
|
|
||||||
|
# Base filename for the saved code
|
||||||
|
base_filename = f"{product_id}_{code_type}"
|
||||||
|
image_path = os.path.join(BARCODE_FOLDER, f"{base_filename}.png")
|
||||||
|
|
||||||
|
# Generate the requested code type
|
||||||
|
if code_type == 'qrcode':
|
||||||
|
# Generate QR code with product URL
|
||||||
|
product_url = request.host_url.rstrip('/') + f"/arinfo?barcode={product_id}"
|
||||||
|
img = generator.generate_qr_code(product_url)
|
||||||
|
generator.save_image(img, image_path)
|
||||||
|
|
||||||
|
elif code_type == 'ean13':
|
||||||
|
# For EAN-13, make sure the product ID is numeric
|
||||||
|
numeric_id = ''.join(filter(str.isdigit, product_id))
|
||||||
|
img = generator.generate_ean13_barcode(numeric_id)
|
||||||
|
generator.save_image(img, image_path)
|
||||||
|
|
||||||
|
elif code_type == 'code128':
|
||||||
|
img = generator.generate_code128_barcode(data)
|
||||||
|
generator.save_image(img, image_path)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return jsonify({"error": "Invalid code type"}), 400
|
||||||
|
|
||||||
|
# Return the image directly
|
||||||
|
return send_file(image_path, mimetype='image/png')
|
||||||
|
|
||||||
|
@admin_bp.route('/generate_barcode_page/<product_id>')
|
||||||
|
def generate_barcode_page(product_id):
|
||||||
|
"""Show a page with different barcode options for a product"""
|
||||||
|
products = load_products()
|
||||||
|
|
||||||
|
if product_id not in products:
|
||||||
|
flash('Product not found.')
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
|
|
||||||
|
# Extract product data
|
||||||
|
product_data = {}
|
||||||
|
for field in products[product_id]:
|
||||||
|
field_name = field["fieldName"][1:] if field["fieldName"].startswith('_') else field["fieldName"]
|
||||||
|
product_data[field_name] = field["value"]
|
||||||
|
|
||||||
|
# Add product ID
|
||||||
|
product_data['id'] = product_id
|
||||||
|
|
||||||
|
# Check if barcode generation is available
|
||||||
|
dependency_status = {}
|
||||||
|
if BARCODE_GENERATOR_AVAILABLE:
|
||||||
|
dependency_status = BarcodeGenerator.check_dependencies()
|
||||||
|
else:
|
||||||
|
dependency_status = {
|
||||||
|
'qrcode': False,
|
||||||
|
'barcode': False,
|
||||||
|
'pillow': False
|
||||||
|
}
|
||||||
|
|
||||||
|
return render_template('admin/generate_barcode.html',
|
||||||
|
product_id=product_id,
|
||||||
|
product=product_data,
|
||||||
|
dependencies=dependency_status)
|
||||||
8
src/routes/api.py
Normal file
8
src/routes/api.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# API routes blueprint scaffold
|
||||||
|
from flask import Blueprint, jsonify
|
||||||
|
|
||||||
|
api_bp = Blueprint('api', __name__, url_prefix='/api')
|
||||||
|
|
||||||
|
@api_bp.route('/')
|
||||||
|
def api_index():
|
||||||
|
return jsonify({'message': 'API Home'})
|
||||||
93
src/utils/barcode_generator.py
Normal file
93
src/utils/barcode_generator.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Utility for barcode generation
|
||||||
|
import os
|
||||||
|
import io
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
|
||||||
|
try:
|
||||||
|
import qrcode
|
||||||
|
QRCODE_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
QRCODE_AVAILABLE = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from barcode import EAN13, Code128
|
||||||
|
from barcode.writer import ImageWriter
|
||||||
|
BARCODE_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
BARCODE_AVAILABLE = False
|
||||||
|
|
||||||
|
class BarcodeGenerator:
|
||||||
|
@staticmethod
|
||||||
|
def check_dependencies():
|
||||||
|
return {
|
||||||
|
'qrcode': QRCODE_AVAILABLE,
|
||||||
|
'barcode': BARCODE_AVAILABLE,
|
||||||
|
'pillow': True
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_qr_code(data, size=10, border=4):
|
||||||
|
if not QRCODE_AVAILABLE:
|
||||||
|
return BarcodeGenerator._generate_placeholder("QR Code Unavailable\nPlease install 'qrcode' package")
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1,
|
||||||
|
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||||
|
box_size=size,
|
||||||
|
border=border,
|
||||||
|
)
|
||||||
|
qr.add_data(data)
|
||||||
|
qr.make(fit=True)
|
||||||
|
img = qr.make_image(fill_color="black", back_color="white").convert('RGB')
|
||||||
|
return img
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_ean13_barcode(data):
|
||||||
|
if not BARCODE_AVAILABLE:
|
||||||
|
return BarcodeGenerator._generate_placeholder("EAN-13 Unavailable\nPlease install 'python-barcode' package")
|
||||||
|
# EAN-13 requires 12 digits (13th is checksum, auto-calculated)
|
||||||
|
numeric = ''.join(filter(str.isdigit, str(data)))
|
||||||
|
if len(numeric) < 12:
|
||||||
|
numeric = numeric.zfill(12)
|
||||||
|
elif len(numeric) > 12:
|
||||||
|
numeric = numeric[:12]
|
||||||
|
ean = EAN13(numeric, writer=ImageWriter())
|
||||||
|
output = io.BytesIO()
|
||||||
|
ean.write(output)
|
||||||
|
output.seek(0)
|
||||||
|
img = Image.open(output)
|
||||||
|
return img
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_code128_barcode(data):
|
||||||
|
if not BARCODE_AVAILABLE:
|
||||||
|
return BarcodeGenerator._generate_placeholder("Code 128 Unavailable\nPlease install 'python-barcode' package")
|
||||||
|
code128 = Code128(str(data), writer=ImageWriter())
|
||||||
|
output = io.BytesIO()
|
||||||
|
code128.write(output)
|
||||||
|
output.seek(0)
|
||||||
|
img = Image.open(output)
|
||||||
|
return img
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_placeholder(message):
|
||||||
|
img = Image.new('RGB', (300, 150), color=(255, 255, 255))
|
||||||
|
d = ImageDraw.Draw(img)
|
||||||
|
d.rectangle([0, 0, 299, 149], outline=(0, 0, 0), width=2)
|
||||||
|
# Optionally, add multiline text
|
||||||
|
lines = message.split('\n')
|
||||||
|
y = 60
|
||||||
|
for line in lines:
|
||||||
|
d.text((20, y), line, fill=(0, 0, 0))
|
||||||
|
y += 20
|
||||||
|
return img
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def save_image(image, path):
|
||||||
|
image.save(path, format='PNG')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_image_as_bytes(image, format='PNG'):
|
||||||
|
buf = io.BytesIO()
|
||||||
|
image.save(buf, format=format)
|
||||||
|
buf.seek(0)
|
||||||
|
return buf.read()
|
||||||
Reference in New Issue
Block a user