Add QR Code styling
This commit is contained in:
@@ -9,6 +9,7 @@ import io, os, base64, re, uuid, requests, json, qrcode, math
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
# Import the logging handler
|
# Import the logging handler
|
||||||
from logger_handler import AppLogger, log_user_activity, log_database_operations
|
from logger_handler import AppLogger, log_user_activity, log_database_operations
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
# Load environment variables in .env
|
# Load environment variables in .env
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
@@ -109,6 +110,16 @@ class QRCode(db.Model):
|
|||||||
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True)
|
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True)
|
||||||
# The project_id field:
|
# The project_id field:
|
||||||
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True)
|
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=True)
|
||||||
|
# QR Code Customization fields
|
||||||
|
fill_color = db.Column(db.String(7), default="#000000") # Hex color
|
||||||
|
back_color = db.Column(db.String(7), default="#FFFFFF") # Background color
|
||||||
|
box_size = db.Column(db.Integer, default=10)
|
||||||
|
border = db.Column(db.Integer, default=4)
|
||||||
|
error_correction = db.Column(db.String(1), default='L')
|
||||||
|
style_id = db.Column(db.Integer, db.ForeignKey('qr_code_styles.id'), nullable=True)
|
||||||
|
|
||||||
|
# Relationship to style
|
||||||
|
style = db.relationship('QRCodeStyle', backref='qr_codes')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_coordinates(self):
|
def has_coordinates(self):
|
||||||
@@ -129,6 +140,24 @@ class QRCode(db.Model):
|
|||||||
self.coordinate_accuracy = accuracy
|
self.coordinate_accuracy = accuracy
|
||||||
self.coordinates_updated_date = datetime.utcnow()
|
self.coordinates_updated_date = datetime.utcnow()
|
||||||
|
|
||||||
|
class QRCodeStyle(db.Model):
|
||||||
|
"""QR Code customization styles"""
|
||||||
|
__tablename__ = 'qr_code_styles'
|
||||||
|
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(100), nullable=False) # Style name
|
||||||
|
fill_color = db.Column(db.String(7), default="#000000") # Hex color for QR modules
|
||||||
|
back_color = db.Column(db.String(7), default="#FFFFFF") # Hex color for background
|
||||||
|
box_size = db.Column(db.Integer, default=10) # Size of each QR module
|
||||||
|
border = db.Column(db.Integer, default=4) # Border size
|
||||||
|
error_correction = db.Column(db.String(1), default='L') # L, M, Q, H
|
||||||
|
is_default = db.Column(db.Boolean, default=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
created_by = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'<QRCodeStyle {self.name}>'
|
||||||
|
|
||||||
class Project(db.Model):
|
class Project(db.Model):
|
||||||
"""
|
"""
|
||||||
Project model to organize QR codes by projects
|
Project model to organize QR codes by projects
|
||||||
@@ -1131,8 +1160,55 @@ def is_admin_user(user_id):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Utility function to generate QR code
|
# Utility function to generate QR code
|
||||||
def generate_qr_code(data):
|
def generate_qr_code(data, fill_color="black", back_color="white", box_size=10, border=4, error_correction='L'):
|
||||||
"""Generate QR code image and return as base64 string"""
|
# Error correction mapping
|
||||||
|
error_correction_map = {
|
||||||
|
'L': qrcode.constants.ERROR_CORRECT_L,
|
||||||
|
'M': qrcode.constants.ERROR_CORRECT_M,
|
||||||
|
'Q': qrcode.constants.ERROR_CORRECT_Q,
|
||||||
|
'H': qrcode.constants.ERROR_CORRECT_H
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1,
|
||||||
|
error_correction=error_correction_map.get(error_correction, qrcode.constants.ERROR_CORRECT_L),
|
||||||
|
box_size=int(box_size),
|
||||||
|
border=int(border),
|
||||||
|
)
|
||||||
|
qr.add_data(data)
|
||||||
|
qr.make(fit=True)
|
||||||
|
|
||||||
|
# Generate QR code image with custom colors
|
||||||
|
img = qr.make_image(fill_color=fill_color, back_color=back_color)
|
||||||
|
|
||||||
|
# Convert to base64
|
||||||
|
buffer = io.BytesIO()
|
||||||
|
img.save(buffer, format='PNG')
|
||||||
|
img_str = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
|
# Log successful generation if logger is available
|
||||||
|
try:
|
||||||
|
logger_handler.log_qr_code_generated(
|
||||||
|
data_length=len(data),
|
||||||
|
fill_color=fill_color,
|
||||||
|
back_color=back_color,
|
||||||
|
box_size=box_size,
|
||||||
|
border=border,
|
||||||
|
error_correction=error_correction
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
pass # Ignore logging errors
|
||||||
|
|
||||||
|
return img_str
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger_handler.log_database_error('qr_code_generation', e)
|
||||||
|
# Return default QR code on error
|
||||||
|
return generate_default_qr_code(data)
|
||||||
|
|
||||||
|
def generate_default_qr_code(data):
|
||||||
|
"""Fallback function for basic QR code generation"""
|
||||||
qr = qrcode.QRCode(
|
qr = qrcode.QRCode(
|
||||||
version=1,
|
version=1,
|
||||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||||
@@ -1144,13 +1220,22 @@ def generate_qr_code(data):
|
|||||||
|
|
||||||
img = qr.make_image(fill_color="black", back_color="white")
|
img = qr.make_image(fill_color="black", back_color="white")
|
||||||
|
|
||||||
# Convert to base64
|
|
||||||
buffer = io.BytesIO()
|
buffer = io.BytesIO()
|
||||||
img.save(buffer, format='PNG')
|
img.save(buffer, format='PNG')
|
||||||
img_str = base64.b64encode(buffer.getvalue()).decode()
|
img_str = base64.b64encode(buffer.getvalue()).decode()
|
||||||
|
|
||||||
return img_str
|
return img_str
|
||||||
|
|
||||||
|
def get_qr_styling(qr_code):
|
||||||
|
"""Extract QR code styling parameters from database record"""
|
||||||
|
return {
|
||||||
|
'fill_color': getattr(qr_code, 'fill_color', '#000000') or '#000000',
|
||||||
|
'back_color': getattr(qr_code, 'back_color', '#FFFFFF') or '#FFFFFF',
|
||||||
|
'box_size': getattr(qr_code, 'box_size', 10) or 10,
|
||||||
|
'border': getattr(qr_code, 'border', 4) or 4,
|
||||||
|
'error_correction': getattr(qr_code, 'error_correction', 'L') or 'L'
|
||||||
|
}
|
||||||
|
|
||||||
@app.template_filter('strftime')
|
@app.template_filter('strftime')
|
||||||
def strftime_filter(value, format='%m/%d/%Y'):
|
def strftime_filter(value, format='%m/%d/%Y'):
|
||||||
"""Format datetime/date/string as strftime"""
|
"""Format datetime/date/string as strftime"""
|
||||||
@@ -2648,9 +2733,10 @@ def api_active_projects():
|
|||||||
@login_required
|
@login_required
|
||||||
@log_database_operations('qr_code_creation')
|
@log_database_operations('qr_code_creation')
|
||||||
def create_qr_code():
|
def create_qr_code():
|
||||||
"""Enhanced create QR code with readable URL format and coordinates saving"""
|
"""Enhanced create QR code with customization options"""
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
try:
|
try:
|
||||||
|
# Existing form data
|
||||||
name = request.form['name']
|
name = request.form['name']
|
||||||
location = request.form['location']
|
location = request.form['location']
|
||||||
location_address = request.form['location_address']
|
location_address = request.form['location_address']
|
||||||
@@ -2662,6 +2748,20 @@ def create_qr_code():
|
|||||||
longitude = request.form.get('longitude')
|
longitude = request.form.get('longitude')
|
||||||
coordinate_accuracy = request.form.get('coordinate_accuracy', 'geocoded')
|
coordinate_accuracy = request.form.get('coordinate_accuracy', 'geocoded')
|
||||||
|
|
||||||
|
# NEW: QR Code customization data
|
||||||
|
fill_color = request.form.get('fill_color', '#000000')
|
||||||
|
back_color = request.form.get('back_color', '#FFFFFF')
|
||||||
|
box_size = int(request.form.get('box_size', 10))
|
||||||
|
border = int(request.form.get('border', 4))
|
||||||
|
error_correction = request.form.get('error_correction', 'L')
|
||||||
|
style_id = request.form.get('style_id') # Pre-defined style
|
||||||
|
|
||||||
|
# Validate colors (basic hex validation)
|
||||||
|
if not (fill_color.startswith('#') and len(fill_color) == 7):
|
||||||
|
fill_color = '#000000'
|
||||||
|
if not (back_color.startswith('#') and len(back_color) == 7):
|
||||||
|
back_color = '#FFFFFF'
|
||||||
|
|
||||||
# Convert coordinates to float if they exist
|
# Convert coordinates to float if they exist
|
||||||
address_latitude = None
|
address_latitude = None
|
||||||
address_longitude = None
|
address_longitude = None
|
||||||
@@ -2682,11 +2782,19 @@ def create_qr_code():
|
|||||||
# Validate project_id if provided
|
# Validate project_id if provided
|
||||||
project = None
|
project = None
|
||||||
if project_id:
|
if project_id:
|
||||||
|
try:
|
||||||
project_id = int(project_id)
|
project_id = int(project_id)
|
||||||
project = Project.query.get(project_id)
|
project = Project.query.get(project_id)
|
||||||
if not project or not project.active_status:
|
if not project or not project.active_status:
|
||||||
flash('Selected project is not valid or inactive.', 'error')
|
flash('Selected project is not valid or inactive.', 'error')
|
||||||
return render_template('create_qr_code.html')
|
return render_template('create_qr_code.html',
|
||||||
|
projects=Project.query.filter_by(active_status=True).all(),
|
||||||
|
styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else [])
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
flash('Invalid project selection.', 'error')
|
||||||
|
return render_template('create_qr_code.html',
|
||||||
|
projects=Project.query.filter_by(active_status=True).all(),
|
||||||
|
styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else [])
|
||||||
|
|
||||||
# Create new QR code record first (without URL and image)
|
# Create new QR code record first (without URL and image)
|
||||||
new_qr_code = QRCode(
|
new_qr_code = QRCode(
|
||||||
@@ -2701,7 +2809,16 @@ def create_qr_code():
|
|||||||
address_latitude=address_latitude,
|
address_latitude=address_latitude,
|
||||||
address_longitude=address_longitude,
|
address_longitude=address_longitude,
|
||||||
coordinate_accuracy=coordinate_accuracy if has_coordinates else None,
|
coordinate_accuracy=coordinate_accuracy if has_coordinates else None,
|
||||||
coordinates_updated_date=datetime.utcnow() if has_coordinates else None
|
coordinates_updated_date=datetime.utcnow() if has_coordinates else None,
|
||||||
|
# NEW: Customization fields (only if columns exist)
|
||||||
|
**({
|
||||||
|
'fill_color': fill_color,
|
||||||
|
'back_color': back_color,
|
||||||
|
'box_size': box_size,
|
||||||
|
'border': border,
|
||||||
|
'error_correction': error_correction,
|
||||||
|
'style_id': int(style_id) if style_id and style_id.isdigit() else None
|
||||||
|
} if hasattr(QRCode, 'fill_color') else {})
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add to session and flush to get the ID
|
# Add to session and flush to get the ID
|
||||||
@@ -2711,9 +2828,16 @@ def create_qr_code():
|
|||||||
# Now generate the readable URL using the ID
|
# Now generate the readable URL using the ID
|
||||||
qr_url = generate_qr_url(name, new_qr_code.id)
|
qr_url = generate_qr_url(name, new_qr_code.id)
|
||||||
|
|
||||||
# Generate QR code data with the destination URL
|
# Generate QR code data with the destination URL and custom styling
|
||||||
qr_data = f"{request.url_root}qr/{qr_url}"
|
qr_data = f"{request.url_root}qr/{qr_url}"
|
||||||
qr_image = generate_qr_code(qr_data)
|
qr_image = generate_qr_code(
|
||||||
|
data=qr_data,
|
||||||
|
fill_color=fill_color,
|
||||||
|
back_color=back_color,
|
||||||
|
box_size=box_size,
|
||||||
|
border=border,
|
||||||
|
error_correction=error_correction
|
||||||
|
)
|
||||||
|
|
||||||
# Update the QR code with the URL and image
|
# Update the QR code with the URL and image
|
||||||
new_qr_code.qr_url = qr_url
|
new_qr_code.qr_url = qr_url
|
||||||
@@ -2722,41 +2846,32 @@ def create_qr_code():
|
|||||||
# Now commit all changes
|
# Now commit all changes
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Enhanced logging with project and coordinates information
|
# Enhanced logging with customization information
|
||||||
log_data = {
|
|
||||||
'qr_code_id': new_qr_code.id,
|
|
||||||
'name': name,
|
|
||||||
'location': location,
|
|
||||||
'qr_url': qr_url, # Log the readable URL
|
|
||||||
'project_id': project_id,
|
|
||||||
'project_name': project.name if project else None,
|
|
||||||
'has_coordinates': has_coordinates,
|
|
||||||
'latitude': address_latitude,
|
|
||||||
'longitude': address_longitude,
|
|
||||||
'coordinate_accuracy': coordinate_accuracy
|
|
||||||
}
|
|
||||||
qr_data_for_logging = {
|
|
||||||
'location': location,
|
|
||||||
'location_address': location_address,
|
|
||||||
'location_event': location_event,
|
|
||||||
'has_coordinates': has_coordinates,
|
|
||||||
'latitude': address_latitude,
|
|
||||||
'longitude': address_longitude,
|
|
||||||
'coordinate_accuracy': coordinate_accuracy
|
|
||||||
}
|
|
||||||
|
|
||||||
logger_handler.log_qr_code_created(
|
logger_handler.log_qr_code_created(
|
||||||
qr_code_id=new_qr_code.id,
|
qr_code_id=new_qr_code.id,
|
||||||
qr_code_name=name,
|
qr_code_name=name,
|
||||||
created_by_user_id=session['user_id'],
|
created_by_user_id=session['user_id'],
|
||||||
qr_data=qr_data_for_logging
|
qr_data={
|
||||||
|
'location': location,
|
||||||
|
'location_address': location_address,
|
||||||
|
'location_event': location_event,
|
||||||
|
'has_coordinates': has_coordinates,
|
||||||
|
'customization': {
|
||||||
|
'fill_color': fill_color,
|
||||||
|
'back_color': back_color,
|
||||||
|
'box_size': box_size,
|
||||||
|
'border': border,
|
||||||
|
'error_correction': error_correction
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Success message with coordinates info
|
# Success message with customization info
|
||||||
project_info = f" in project '{project.name}'" if project else ""
|
project_info = f" in project '{project.name}'" if project else ""
|
||||||
coord_info = f" with coordinates ({new_qr_code.coordinates_display})" if has_coordinates else ""
|
coord_info = f" with coordinates ({new_qr_code.coordinates_display})" if has_coordinates else ""
|
||||||
|
style_info = f" with custom styling (Fill: {fill_color}, Background: {back_color})"
|
||||||
|
|
||||||
flash(f'QR Code "{name}" created successfully{project_info}{coord_info}! URL: {qr_url}', 'success')
|
flash(f'QR Code "{name}" created successfully{project_info}{coord_info}{style_info}! URL: {qr_url}', 'success')
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -2765,15 +2880,17 @@ def create_qr_code():
|
|||||||
flash('QR Code creation failed. Please try again.', 'error')
|
flash('QR Code creation failed. Please try again.', 'error')
|
||||||
print(f"❌ QR Code creation error: {e}")
|
print(f"❌ QR Code creation error: {e}")
|
||||||
|
|
||||||
# Get active projects for dropdown
|
# Get active projects and styles for dropdown
|
||||||
projects = Project.query.filter_by(active_status=True).order_by(Project.name.asc()).all()
|
projects = Project.query.filter_by(active_status=True).order_by(Project.name.asc()).all()
|
||||||
return render_template('create_qr_code.html', projects=projects)
|
styles = QRCodeStyle.query.order_by(QRCodeStyle.name.asc()).all() if 'QRCodeStyle' in globals() else []
|
||||||
|
|
||||||
|
return render_template('create_qr_code.html', projects=projects, styles=styles)
|
||||||
|
|
||||||
@app.route('/qr-codes/<int:qr_id>/edit', methods=['GET', 'POST'])
|
@app.route('/qr-codes/<int:qr_id>/edit', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@log_database_operations('qr_code_edit')
|
@log_database_operations('qr_code_edit')
|
||||||
def edit_qr_code(qr_id):
|
def edit_qr_code(qr_id):
|
||||||
"""Enhanced edit QR code with project association and URL regeneration - COORDINATE FIX"""
|
"""Enhanced edit QR code with customization support"""
|
||||||
try:
|
try:
|
||||||
qr_code = QRCode.query.get_or_404(qr_id)
|
qr_code = QRCode.query.get_or_404(qr_id)
|
||||||
|
|
||||||
@@ -2788,7 +2905,10 @@ def edit_qr_code(qr_id):
|
|||||||
'qr_url': qr_code.qr_url,
|
'qr_url': qr_code.qr_url,
|
||||||
'address_latitude': qr_code.address_latitude,
|
'address_latitude': qr_code.address_latitude,
|
||||||
'address_longitude': qr_code.address_longitude,
|
'address_longitude': qr_code.address_longitude,
|
||||||
'coordinate_accuracy': qr_code.coordinate_accuracy
|
'coordinate_accuracy': qr_code.coordinate_accuracy,
|
||||||
|
# Track old styling
|
||||||
|
'fill_color': getattr(qr_code, 'fill_color', '#000000'),
|
||||||
|
'back_color': getattr(qr_code, 'back_color', '#FFFFFF')
|
||||||
}
|
}
|
||||||
|
|
||||||
# Update QR code fields
|
# Update QR code fields
|
||||||
@@ -2798,12 +2918,11 @@ def edit_qr_code(qr_id):
|
|||||||
qr_code.location_address = request.form['location_address']
|
qr_code.location_address = request.form['location_address']
|
||||||
qr_code.location_event = request.form.get('location_event', '')
|
qr_code.location_event = request.form.get('location_event', '')
|
||||||
|
|
||||||
# SIMPLIFIED COORDINATE HANDLING - Remove validation requirement
|
# Handle coordinates
|
||||||
latitude = request.form.get('address_latitude') # Changed from 'latitude' to match form
|
latitude = request.form.get('address_latitude')
|
||||||
longitude = request.form.get('address_longitude') # Changed from 'longitude' to match form
|
longitude = request.form.get('address_longitude')
|
||||||
coordinate_accuracy = request.form.get('coordinate_accuracy', 'geocoded')
|
coordinate_accuracy = request.form.get('coordinate_accuracy', 'geocoded')
|
||||||
|
|
||||||
# Update coordinates if provided, or keep existing ones
|
|
||||||
if latitude and longitude:
|
if latitude and longitude:
|
||||||
try:
|
try:
|
||||||
qr_code.address_latitude = float(latitude)
|
qr_code.address_latitude = float(latitude)
|
||||||
@@ -2811,19 +2930,16 @@ def edit_qr_code(qr_id):
|
|||||||
qr_code.coordinate_accuracy = coordinate_accuracy
|
qr_code.coordinate_accuracy = coordinate_accuracy
|
||||||
qr_code.coordinates_updated_date = datetime.utcnow()
|
qr_code.coordinates_updated_date = datetime.utcnow()
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
# If invalid coordinates, keep existing ones
|
|
||||||
pass
|
pass
|
||||||
elif latitude == '' and longitude == '':
|
elif latitude == '' and longitude == '':
|
||||||
# Explicitly clear coordinates if empty strings are sent
|
|
||||||
qr_code.address_latitude = None
|
qr_code.address_latitude = None
|
||||||
qr_code.address_longitude = None
|
qr_code.address_longitude = None
|
||||||
qr_code.coordinate_accuracy = None
|
qr_code.coordinate_accuracy = None
|
||||||
qr_code.coordinates_updated_date = None
|
qr_code.coordinates_updated_date = None
|
||||||
# If no coordinate data is provided, keep existing coordinates unchanged
|
|
||||||
|
|
||||||
# Handle project association - FIXED LOGIC
|
# Handle project association
|
||||||
new_project_id = request.form.get('project_id')
|
new_project_id = request.form.get('project_id')
|
||||||
if new_project_id and new_project_id.strip(): # Check for non-empty string
|
if new_project_id and new_project_id.strip():
|
||||||
try:
|
try:
|
||||||
new_project_id = int(new_project_id)
|
new_project_id = int(new_project_id)
|
||||||
project = Project.query.get(new_project_id)
|
project = Project.query.get(new_project_id)
|
||||||
@@ -2831,104 +2947,70 @@ def edit_qr_code(qr_id):
|
|||||||
qr_code.project_id = new_project_id
|
qr_code.project_id = new_project_id
|
||||||
else:
|
else:
|
||||||
flash('Selected project is not valid or inactive.', 'error')
|
flash('Selected project is not valid or inactive.', 'error')
|
||||||
return render_template('edit_qr_code.html', qr_code=qr_code, projects=Project.query.filter_by(active_status=True).all())
|
return render_template('edit_qr_code.html', qr_code=qr_code,
|
||||||
|
projects=Project.query.filter_by(active_status=True).all(),
|
||||||
|
styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else [])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
# Invalid project_id format
|
|
||||||
flash('Invalid project selection.', 'error')
|
flash('Invalid project selection.', 'error')
|
||||||
return render_template('edit_qr_code.html', qr_code=qr_code, projects=Project.query.filter_by(active_status=True).all())
|
return render_template('edit_qr_code.html', qr_code=qr_code,
|
||||||
|
projects=Project.query.filter_by(active_status=True).all(),
|
||||||
|
styles=QRCodeStyle.query.all() if 'QRCodeStyle' in globals() else [])
|
||||||
else:
|
else:
|
||||||
# Empty or None project_id means unassign from project
|
|
||||||
qr_code.project_id = None
|
qr_code.project_id = None
|
||||||
|
|
||||||
# Regenerate URL if name changed
|
# Handle QR code customization (only if columns exist)
|
||||||
|
fill_color = request.form.get('fill_color', '#000000')
|
||||||
|
back_color = request.form.get('back_color', '#FFFFFF')
|
||||||
|
box_size = int(request.form.get('box_size', 10))
|
||||||
|
border = int(request.form.get('border', 4))
|
||||||
|
error_correction = request.form.get('error_correction', 'L')
|
||||||
|
style_id = request.form.get('style_id')
|
||||||
|
|
||||||
|
# Update styling fields if they exist
|
||||||
|
if hasattr(qr_code, 'fill_color'):
|
||||||
|
qr_code.fill_color = fill_color
|
||||||
|
qr_code.back_color = back_color
|
||||||
|
qr_code.box_size = box_size
|
||||||
|
qr_code.border = border
|
||||||
|
qr_code.error_correction = error_correction
|
||||||
|
qr_code.style_id = int(style_id) if style_id and style_id.isdigit() else None
|
||||||
|
|
||||||
|
# Check if QR code needs regeneration
|
||||||
name_changed = old_data['name'] != new_name
|
name_changed = old_data['name'] != new_name
|
||||||
|
styling_changed = (hasattr(qr_code, 'fill_color') and
|
||||||
|
(old_data['fill_color'] != fill_color or
|
||||||
|
old_data['back_color'] != back_color))
|
||||||
|
|
||||||
if name_changed:
|
if name_changed:
|
||||||
new_qr_url = generate_qr_url(new_name, qr_code.id)
|
new_qr_url = generate_qr_url(new_name, qr_code.id)
|
||||||
|
|
||||||
# Regenerate QR code image with new URL
|
|
||||||
qr_data = f"{request.url_root}qr/{new_qr_url}"
|
|
||||||
new_qr_image = generate_qr_code(qr_data)
|
|
||||||
|
|
||||||
qr_code.qr_url = new_qr_url
|
qr_code.qr_url = new_qr_url
|
||||||
qr_code.qr_code_image = new_qr_image
|
|
||||||
|
|
||||||
# ENHANCED CHANGE DETECTION
|
# Regenerate QR code if name or styling changed
|
||||||
project_changed = old_data['project_id'] != qr_code.project_id
|
if name_changed or styling_changed:
|
||||||
coordinates_changed = (old_data['address_latitude'] != qr_code.address_latitude or
|
qr_data = f"{request.url_root}qr/{qr_code.qr_url}"
|
||||||
old_data['address_longitude'] != qr_code.address_longitude)
|
|
||||||
|
# Use new styling if available, otherwise use defaults
|
||||||
|
styling = get_qr_styling(qr_code)
|
||||||
|
qr_code.qr_code_image = generate_qr_code(
|
||||||
|
data=qr_data,
|
||||||
|
fill_color=styling['fill_color'],
|
||||||
|
back_color=styling['back_color'],
|
||||||
|
box_size=styling['box_size'],
|
||||||
|
border=styling['border'],
|
||||||
|
error_correction=styling['error_correction']
|
||||||
|
)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Log changes with improved tracking
|
# Success message
|
||||||
new_data = {
|
flash(f'QR Code "{qr_code.name}" updated successfully!', 'success')
|
||||||
'name': qr_code.name,
|
|
||||||
'location': qr_code.location,
|
|
||||||
'location_address': qr_code.location_address,
|
|
||||||
'location_event': qr_code.location_event,
|
|
||||||
'project_id': qr_code.project_id,
|
|
||||||
'qr_url': qr_code.qr_url,
|
|
||||||
'address_latitude': qr_code.address_latitude,
|
|
||||||
'address_longitude': qr_code.address_longitude,
|
|
||||||
'coordinate_accuracy': qr_code.coordinate_accuracy
|
|
||||||
}
|
|
||||||
|
|
||||||
changes = {}
|
|
||||||
for key in old_data:
|
|
||||||
if old_data[key] != new_data[key]:
|
|
||||||
changes[key] = {'old': old_data[key], 'new': new_data[key]}
|
|
||||||
|
|
||||||
# Enhanced logging for all changes
|
|
||||||
if changes:
|
|
||||||
logger_handler.logger.info(f"User {session['username']} updated QR code {qr_id}: {json.dumps(changes)}")
|
|
||||||
|
|
||||||
# Log specific change actions
|
|
||||||
if project_changed:
|
|
||||||
old_project_name = None
|
|
||||||
new_project_name = None
|
|
||||||
|
|
||||||
if old_data['project_id']:
|
|
||||||
old_project = Project.query.get(old_data['project_id'])
|
|
||||||
old_project_name = old_project.name if old_project else f"Project ID {old_data['project_id']}"
|
|
||||||
|
|
||||||
if qr_code.project_id:
|
|
||||||
new_project = Project.query.get(qr_code.project_id)
|
|
||||||
new_project_name = new_project.name if new_project else f"Project ID {qr_code.project_id}"
|
|
||||||
|
|
||||||
project_change_msg = f"QR code '{qr_code.name}' project changed from '{old_project_name or 'No Project'}' to '{new_project_name or 'No Project'}'"
|
|
||||||
logger_handler.logger.info(f"User {session['username']} - {project_change_msg}")
|
|
||||||
|
|
||||||
if coordinates_changed:
|
|
||||||
coord_change_msg = f"QR code '{qr_code.name}' coordinates updated"
|
|
||||||
if qr_code.has_coordinates:
|
|
||||||
coord_change_msg += f" to {qr_code.coordinates_display}"
|
|
||||||
else:
|
|
||||||
coord_change_msg += " (coordinates cleared)"
|
|
||||||
logger_handler.logger.info(f"User {session['username']} - {coord_change_msg}")
|
|
||||||
|
|
||||||
# Success message with detailed information
|
|
||||||
url_message = f" URL updated to: {qr_code.qr_url}" if name_changed else ""
|
|
||||||
project_message = ""
|
|
||||||
coord_message = ""
|
|
||||||
|
|
||||||
if project_changed:
|
|
||||||
if qr_code.project_id:
|
|
||||||
current_project = Project.query.get(qr_code.project_id)
|
|
||||||
project_message = f" Project updated to: {current_project.name if current_project else 'Unknown'}"
|
|
||||||
else:
|
|
||||||
project_message = " Removed from project"
|
|
||||||
|
|
||||||
if coordinates_changed:
|
|
||||||
if qr_code.has_coordinates:
|
|
||||||
coord_message = f" Coordinates updated: {qr_code.coordinates_display}"
|
|
||||||
else:
|
|
||||||
coord_message = " Coordinates cleared"
|
|
||||||
|
|
||||||
flash(f'QR Code "{qr_code.name}" updated successfully!{url_message}{project_message}{coord_message}', 'success')
|
|
||||||
return redirect(url_for('dashboard'))
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
# Get active projects for dropdown
|
# GET request - render edit form
|
||||||
projects = Project.query.filter_by(active_status=True).order_by(Project.name.asc()).all()
|
projects = Project.query.filter_by(active_status=True).order_by(Project.name.asc()).all()
|
||||||
return render_template('edit_qr_code.html', qr_code=qr_code, projects=projects)
|
styles = QRCodeStyle.query.order_by(QRCodeStyle.name.asc()).all() if 'QRCodeStyle' in globals() else []
|
||||||
|
|
||||||
|
return render_template('edit_qr_code.html', qr_code=qr_code, projects=projects, styles=styles)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
@@ -4508,7 +4590,17 @@ def update_existing_qr_codes():
|
|||||||
# Generate QR image if missing
|
# Generate QR image if missing
|
||||||
if not qr_code.qr_code_image:
|
if not qr_code.qr_code_image:
|
||||||
qr_data = f"{request.url_root}qr/{qr_code.qr_url}"
|
qr_data = f"{request.url_root}qr/{qr_code.qr_url}"
|
||||||
qr_code.qr_code_image = generate_qr_code(qr_data)
|
|
||||||
|
# Use styling from database if available, otherwise defaults
|
||||||
|
styling = get_qr_styling(qr_code)
|
||||||
|
qr_code.qr_code_image = generate_qr_code(
|
||||||
|
data=qr_data,
|
||||||
|
fill_color=styling['fill_color'],
|
||||||
|
back_color=styling['back_color'],
|
||||||
|
box_size=styling['box_size'],
|
||||||
|
border=styling['border'],
|
||||||
|
error_correction=styling['error_correction']
|
||||||
|
)
|
||||||
|
|
||||||
updated_count += 1
|
updated_count += 1
|
||||||
|
|
||||||
@@ -4524,8 +4616,50 @@ def update_existing_qr_codes():
|
|||||||
logger_handler.logger.info(f"Updated {updated_count} existing QR codes with missing URLs/images")
|
logger_handler.logger.info(f"Updated {updated_count} existing QR codes with missing URLs/images")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
db.session.rollback()
|
|
||||||
logger_handler.log_database_error('update_existing_qr_codes', e)
|
logger_handler.log_database_error('update_existing_qr_codes', e)
|
||||||
|
print(f"Error updating existing QR codes: {e}")
|
||||||
|
|
||||||
|
def add_qr_customization_columns():
|
||||||
|
"""Add QR code customization columns to existing table"""
|
||||||
|
try:
|
||||||
|
with app.app_context():
|
||||||
|
# Add columns to QRCode table if they don't exist
|
||||||
|
db.engine.execute("""
|
||||||
|
ALTER TABLE qr_codes
|
||||||
|
ADD COLUMN IF NOT EXISTS fill_color VARCHAR(7) DEFAULT '#000000',
|
||||||
|
ADD COLUMN IF NOT EXISTS back_color VARCHAR(7) DEFAULT '#FFFFFF',
|
||||||
|
ADD COLUMN IF NOT EXISTS box_size INT DEFAULT 10,
|
||||||
|
ADD COLUMN IF NOT EXISTS border INT DEFAULT 4,
|
||||||
|
ADD COLUMN IF NOT EXISTS error_correction VARCHAR(1) DEFAULT 'L',
|
||||||
|
ADD COLUMN IF NOT EXISTS style_id INT,
|
||||||
|
ADD FOREIGN KEY (style_id) REFERENCES qr_code_styles(id)
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Create QRCodeStyle table
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# Insert default styles
|
||||||
|
default_styles = [
|
||||||
|
QRCodeStyle(name="Classic Black", fill_color="#000000", back_color="#FFFFFF", is_default=True),
|
||||||
|
QRCodeStyle(name="Blue Professional", fill_color="#2563eb", back_color="#FFFFFF"),
|
||||||
|
QRCodeStyle(name="Green Success", fill_color="#10b981", back_color="#FFFFFF"),
|
||||||
|
QRCodeStyle(name="Red Alert", fill_color="#ef4444", back_color="#FFFFFF"),
|
||||||
|
QRCodeStyle(name="Dark Mode", fill_color="#FFFFFF", back_color="#1f2937"),
|
||||||
|
QRCodeStyle(name="High Contrast", fill_color="#000000", back_color="#FFFF00"),
|
||||||
|
QRCodeStyle(name="Corporate Blue", fill_color="#1e40af", back_color="#f8fafc"),
|
||||||
|
QRCodeStyle(name="Minimalist Gray", fill_color="#6b7280", back_color="#FFFFFF")
|
||||||
|
]
|
||||||
|
|
||||||
|
for style in default_styles:
|
||||||
|
if not QRCodeStyle.query.filter_by(name=style.name).first():
|
||||||
|
db.session.add(style)
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
print("QR Code customization tables and default styles created successfully!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.session.rollback()
|
||||||
|
print(f"Error adding QR customization columns: {e}")
|
||||||
|
|
||||||
def add_coordinate_columns():
|
def add_coordinate_columns():
|
||||||
"""Add coordinate columns to existing qr_codes table (MySQL compatible)"""
|
"""Add coordinate columns to existing qr_codes table (MySQL compatible)"""
|
||||||
@@ -4639,6 +4773,7 @@ if __name__ == '__main__':
|
|||||||
# Initialize database and logging
|
# Initialize database and logging
|
||||||
create_tables()
|
create_tables()
|
||||||
update_existing_qr_codes()
|
update_existing_qr_codes()
|
||||||
|
add_qr_customization_columns()
|
||||||
|
|
||||||
# Log application startup
|
# Log application startup
|
||||||
logger_handler.logger.info("QR Attendance Management System started successfully")
|
logger_handler.logger.info("QR Attendance Management System started successfully")
|
||||||
|
|||||||
@@ -473,6 +473,17 @@ class AppLogger:
|
|||||||
'data': event_data
|
'data': event_data
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
def log_qr_code_generated(self, data_length, fill_color, back_color, box_size, border, error_correction):
|
||||||
|
"""Log QR code generation with customization details"""
|
||||||
|
try:
|
||||||
|
self.logger.info(f"QR code generated with customization - "
|
||||||
|
f"Data length: {data_length}, "
|
||||||
|
f"Fill: {fill_color}, Background: {back_color}, "
|
||||||
|
f"Box size: {box_size}, Border: {border}, "
|
||||||
|
f"Error correction: {error_correction}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to log QR code generation: {e}")
|
||||||
|
|
||||||
# DATABASE ERROR LOGGING METHODS
|
# DATABASE ERROR LOGGING METHODS
|
||||||
|
|
||||||
def log_database_error(self, operation, error, query=None, parameters=None):
|
def log_database_error(self, operation, error, query=None, parameters=None):
|
||||||
|
|||||||
+796
-286
File diff suppressed because it is too large
Load Diff
+766
-402
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user