From f781cdf602c6ee8e593ee9f779c6ace20d5e752f Mon Sep 17 00:00:00 2001 From: Matt Hills Date: Mon, 20 Oct 2025 17:11:26 -0400 Subject: [PATCH] Add dynamic custom field display and remove redundant admin routes Major improvements to tenant management: 1. Dynamic field display - Product lists now automatically show all custom fields based on tenant configuration (e.g., inventory, custom attributes) 2. Fixed Jinja2 template scoping issue - Product field values now correctly display in edit forms using selectattr filter 3. Simplified URL structure - Removed redundant /tenant/admin/ routes, all product management now at /tenant/ root 4. Enhanced tenant landing page - Added delete functionality with confirmation modal, shows all custom fields except images 5. Updated all routes and redirects to use simplified paths (/tenant/add, /tenant/edit, etc.) This consolidates the interface so /tenant/ serves as the main dashboard with full product management capabilities, while /tenant/settings handles configuration. --- src/app.py | 30 +++---- src/routes/admin.py | 37 ++++----- src/templates/admin/add_product.html | 4 +- src/templates/admin/edit_product.html | 17 ++-- src/templates/admin/generate_barcode.html | 2 +- src/templates/admin/index.html | 58 +++++++------- src/templates/index.html | 97 +++++++++++++++++------ 7 files changed, 142 insertions(+), 103 deletions(-) diff --git a/src/app.py b/src/app.py index 2842682..456a73a 100644 --- a/src/app.py +++ b/src/app.py @@ -70,11 +70,12 @@ def tenant_index(tenant_id): if tenant is None: # Reserved tenant ID or invalid return jsonify({"error": f"'{tenant_id}' is a reserved name and cannot be used as a tenant ID"}), 404 - + # Load products for this tenant products = load_products(tenant_id) - - return render_template('index.html', tenant=tenant, products=products) + custom_fields = database.get_custom_ar_fields(tenant_id) + + return render_template('index.html', tenant=tenant, products=products, custom_fields=custom_fields) @app.route('//settings') def tenant_settings(tenant_id): @@ -292,21 +293,16 @@ def serve_barcode(tenant_id, filename): app.logger.error(f"Barcode generation error: {str(e)}") return jsonify({"error": "Failed to generate barcode"}), 500 -# Import the admin routes directly and register them with tenant support -from routes.admin import (index as admin_index, add_product, edit_product, - delete_product, view_product, generate_barcode, - generate_barcode_page, manage_credentials, manage_ar_fields) +# Import product management routes +from routes.admin import (add_product, edit_product, delete_product, + generate_barcode, generate_barcode_page) -# Register admin routes with tenant prefix -app.add_url_rule('//admin/', 'admin.index', admin_index) -app.add_url_rule('//admin/add', 'admin.add_product', add_product, methods=['GET', 'POST']) -app.add_url_rule('//admin/edit/', 'admin.edit_product', edit_product, methods=['GET', 'POST']) -app.add_url_rule('//admin/delete/', 'admin.delete_product', delete_product, methods=['POST']) -app.add_url_rule('//admin/view/', 'admin.view_product', view_product) -app.add_url_rule('//admin/generate_barcode//', 'admin.generate_barcode', generate_barcode) -app.add_url_rule('//admin/generate_barcode_page/', 'admin.generate_barcode_page', generate_barcode_page) -app.add_url_rule('//admin/credentials', 'admin.manage_credentials', manage_credentials, methods=['GET', 'POST']) -app.add_url_rule('//admin/ar_fields', 'admin.manage_ar_fields', manage_ar_fields, methods=['GET', 'POST']) +# Register product management routes (remove /admin/ from paths) +app.add_url_rule('//add', 'admin.add_product', add_product, methods=['GET', 'POST']) +app.add_url_rule('//edit/', 'admin.edit_product', edit_product, methods=['GET', 'POST']) +app.add_url_rule('//delete/', 'admin.delete_product', delete_product, methods=['POST']) +app.add_url_rule('//generate_barcode//', 'admin.generate_barcode', generate_barcode) +app.add_url_rule('//generate_barcode_page/', 'admin.generate_barcode_page', generate_barcode_page) # Register API routes with tenant prefix from routes.api import api_index diff --git a/src/routes/admin.py b/src/routes/admin.py index 82c692c..1b24498 100644 --- a/src/routes/admin.py +++ b/src/routes/admin.py @@ -38,7 +38,8 @@ def save_products(products): def index(tenant_id): products = load_products(tenant_id) tenant = database.get_or_create_tenant(tenant_id) - return render_template('admin/index.html', products=products, tenant=tenant) + custom_fields = database.get_custom_ar_fields(tenant_id) + return render_template('admin/index.html', products=products, tenant=tenant, custom_fields=custom_fields) def add_product(tenant_id): # Get custom AR fields for this tenant @@ -172,26 +173,26 @@ def add_product(tenant_id): database.save_product_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type']) flash('Product added successfully!') - return redirect(url_for('admin.index', tenant_id=tenant_id)) + return redirect(f'/{tenant_id}/') return render_template('admin/add_product.html', tenant_id=tenant_id, custom_fields=custom_fields) def edit_product(tenant_id, product_id): products = load_products(tenant_id) custom_fields = database.get_custom_ar_fields(tenant_id) - + if product_id not in products: flash('Product not found.') - return redirect(url_for('admin.index', tenant_id=tenant_id)) + return redirect(f'/{tenant_id}/') if request.method == 'POST': # Handle backward compatibility image upload image_data = None image_mime_type = None - + # Process images after product is saved images_to_save = [] - + # Update fields based on form data updated_product = [] @@ -295,7 +296,7 @@ def edit_product(tenant_id, product_id): database.save_product_image(product_id, tenant_id, img['field_name'], img['data'], img['mime_type']) flash('Product updated successfully!') - return redirect(url_for('admin.index', tenant_id=tenant_id)) + return redirect(f'/{tenant_id}/') return render_template('admin/edit_product.html', product_id=product_id, @@ -305,24 +306,24 @@ def edit_product(tenant_id, product_id): def delete_product(tenant_id, product_id): products = load_products(tenant_id) - + if product_id not in products: flash('Product not found.') - return redirect(url_for('admin.index', tenant_id=tenant_id)) - + return redirect(f'/{tenant_id}/') + # Delete product from database (image is stored in DB) database.delete_product(product_id, tenant_id) - + flash('Product deleted successfully!') - return redirect(url_for('admin.index', tenant_id=tenant_id)) + return redirect(f'/{tenant_id}/') def view_product(tenant_id, product_id): products = load_products(tenant_id) - + if product_id not in products: flash('Product not found.') - return redirect(url_for('admin.index', tenant_id=tenant_id)) - + return redirect(f'/{tenant_id}/') + return render_template('admin/view_product.html', product_id=product_id, product=products[product_id], tenant_id=tenant_id) def generate_barcode(tenant_id, product_id, code_type): @@ -346,10 +347,10 @@ def generate_barcode(tenant_id, product_id, code_type): def generate_barcode_page(tenant_id, product_id): """Show a page with different barcode options for a product""" products = load_products(tenant_id) - + if product_id not in products: flash('Product not found.') - return redirect(url_for('admin.index', tenant_id=tenant_id)) + return redirect(f'/{tenant_id}/') # Extract product data product_data = {} @@ -388,7 +389,7 @@ def manage_credentials(tenant_id): if username and password: database.update_tenant_credentials(tenant_id, username, password) flash('Credentials updated successfully!') - return redirect(url_for('admin.index', tenant_id=tenant_id)) + return redirect(f'/{tenant_id}/') else: flash('Username and password are required.') diff --git a/src/templates/admin/add_product.html b/src/templates/admin/add_product.html index 099495a..d38843b 100644 --- a/src/templates/admin/add_product.html +++ b/src/templates/admin/add_product.html @@ -10,7 +10,7 @@

Add New Product

-
+
@@ -67,7 +67,7 @@ {% endif %}
- Cancel + Cancel
diff --git a/src/templates/admin/edit_product.html b/src/templates/admin/edit_product.html index e7f85ad..16873bb 100644 --- a/src/templates/admin/edit_product.html +++ b/src/templates/admin/edit_product.html @@ -10,7 +10,7 @@

Edit Product

-
+
@@ -21,14 +21,11 @@ {% if field.fieldName != '_id' %}
- - {% set current_value = '' %} - {% for prod_field in product %} - {% if prod_field.fieldName == field.fieldName %} - {% set current_value = prod_field.value %} - {% endif %} - {% endfor %} - + + {# Find current value using selectattr filter #} + {% set matching_fields = product|selectattr('fieldName', 'equalto', field.fieldName)|list %} + {% set current_value = matching_fields[0].value if matching_fields else '' %} + {% if field.fieldType == 'IMAGE_URI' %}
Leave empty to keep the current image.
@@ -93,7 +90,7 @@ {% endif %}
- Cancel + Cancel
diff --git a/src/templates/admin/generate_barcode.html b/src/templates/admin/generate_barcode.html index 19a0d8c..dd94df4 100644 --- a/src/templates/admin/generate_barcode.html +++ b/src/templates/admin/generate_barcode.html @@ -8,7 +8,7 @@

Generate Barcodes for Product: {{ product_id }}

- Back to Products + Back to Products
{% if not dependencies.qrcode or not dependencies.barcode or not dependencies.pillow %} diff --git a/src/templates/admin/index.html b/src/templates/admin/index.html index 4371b28..d005c1c 100644 --- a/src/templates/admin/index.html +++ b/src/templates/admin/index.html @@ -22,46 +22,44 @@ - - - - - + + {% for custom_field in custom_fields %} + {% if custom_field.fieldName != '_id' %} + + {% endif %} + {% endfor %} + {% for product_id, product_data in products.items() %} - - - + {% for custom_field in custom_fields %} + {% if custom_field.fieldName != '_id' %} + + {% endif %} + {% endfor %} - - + {% for custom_field in custom_fields %} + {% if custom_field.fieldName != '_id' and custom_field.fieldType != 'IMAGE_URI' %} + + {% endif %} + {% endfor %} @@ -38,45 +41,42 @@ {% for product_id, product_data in products.items() %} + {% for custom_field in custom_fields %} + {% if custom_field.fieldName != '_id' and custom_field.fieldType != 'IMAGE_URI' %} + + {% endif %} + {% endfor %} - - {% endfor %}
Product IDNamePriceImageActionsProduct ID{{ custom_field.label }}Actions
{{ product_id }} - {% for field in product_data %} - {% if field.fieldName == '_name' %} - {{ field.value }} - {% endif %} - {% endfor %} - - {% for field in product_data %} - {% if field.fieldName == '_price' %} - {{ field.value }} - {% endif %} - {% endfor %} - - {% for field in product_data %} - {% if field.fieldName == '_image' %} -
- Product image -
- {% endif %} - {% endfor %} -
+ {% for field in product_data %} + {% if field.fieldName == custom_field.fieldName %} + {% if custom_field.fieldType == 'IMAGE_URI' %} + {% if field.value %} +
+ {{ custom_field.label }} +
+ {% endif %} + {% else %} + {{ field.value }} + {% endif %} + {% endif %} + {% endfor %} +
Edit Barcodes -
Product IDNamePrice{{ custom_field.label }}Actions
{{ product_id }} + {% set matching_fields = product_data|selectattr('fieldName', 'equalto', custom_field.fieldName)|list %} + {{ matching_fields[0].value if matching_fields else '' }} + - {% for field in product_data %} - {% if field.fieldName == '_name' %} - {{ field.value }} - {% endif %} - {% endfor %} - - {% for field in product_data %} - {% if field.fieldName == '_price' %} - {{ field.value }} - {% endif %} - {% endfor %} - - + Edit - + Barcodes + +
- {% else %}
No products available yet. Click "Add New Product" to get started.
{% endif %}
- +

Getting Started

    @@ -89,4 +89,51 @@
+ + + +{% endblock %} + +{% block extra_js %} + {% endblock %}