Merge pull request #11 from mattintech/feature/default-fields
Add optional default AR fields with proper validation
This commit is contained in:
@@ -10,6 +10,11 @@ def add_product(tenant_id):
|
|||||||
"""Add a new product"""
|
"""Add a new product"""
|
||||||
custom_fields = ARFieldModel.get_all(tenant_id)
|
custom_fields = ARFieldModel.get_all(tenant_id)
|
||||||
|
|
||||||
|
# Prevent adding products if no fields are configured
|
||||||
|
if not custom_fields:
|
||||||
|
flash('Cannot add products. Please configure AR fields first.', 'warning')
|
||||||
|
return redirect(url_for('admin.manage_ar_fields', tenant_id=tenant_id))
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
product_id = request.form.get('product_id')
|
product_id = request.form.get('product_id')
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,20 @@ def index(tenant_id):
|
|||||||
if tenant is None:
|
if tenant is None:
|
||||||
return jsonify({"error": f"'{tenant_id}' is a reserved name and cannot be used as a tenant ID"}), 404
|
return jsonify({"error": f"'{tenant_id}' is a reserved name and cannot be used as a tenant ID"}), 404
|
||||||
|
|
||||||
products = ProductModel.get_all(tenant_id)
|
# Check if we should create default fields for a new tenant
|
||||||
|
create_defaults = request.args.get('create_defaults', '0') == '1'
|
||||||
custom_fields = ARFieldModel.get_all(tenant_id)
|
custom_fields = ARFieldModel.get_all(tenant_id)
|
||||||
|
|
||||||
|
# If no custom fields exist and user requested defaults, create them
|
||||||
|
if create_defaults and not custom_fields:
|
||||||
|
ARFieldModel.create_default_fields(tenant_id)
|
||||||
|
custom_fields = ARFieldModel.get_all(tenant_id)
|
||||||
|
flash('Tenant created with default product fields!', 'success')
|
||||||
|
# Redirect to remove the query parameter
|
||||||
|
return redirect(f'/{tenant_id}/')
|
||||||
|
|
||||||
|
products = ProductModel.get_all(tenant_id)
|
||||||
|
|
||||||
return render_template('index.html', tenant=tenant, products=products, custom_fields=custom_fields)
|
return render_template('index.html', tenant=tenant, products=products, custom_fields=custom_fields)
|
||||||
|
|
||||||
@tenant_bp.route('/delete', methods=['POST'])
|
@tenant_bp.route('/delete', methods=['POST'])
|
||||||
|
|||||||
@@ -82,3 +82,56 @@ class ARFieldModel:
|
|||||||
cursor.execute('DELETE FROM custom_ar_fields WHERE id = ? AND tenant_id = ?',
|
cursor.execute('DELETE FROM custom_ar_fields WHERE id = ? AND tenant_id = ?',
|
||||||
(field_id, tenant_id))
|
(field_id, tenant_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_default_fields(tenant_id: str):
|
||||||
|
"""Create default product fields for a new tenant"""
|
||||||
|
tenant_id = tenant_id.lower()
|
||||||
|
|
||||||
|
default_fields = [
|
||||||
|
{
|
||||||
|
'fieldName': '_name',
|
||||||
|
'label': 'Product Name',
|
||||||
|
'fieldType': 'TEXT',
|
||||||
|
'editable': 'true',
|
||||||
|
'displayOrder': 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldName': '_id',
|
||||||
|
'label': 'Item ID',
|
||||||
|
'fieldType': 'TEXT',
|
||||||
|
'editable': 'false',
|
||||||
|
'displayOrder': 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldName': '_price',
|
||||||
|
'label': 'Sale Price',
|
||||||
|
'fieldType': 'TEXT',
|
||||||
|
'editable': 'true',
|
||||||
|
'displayOrder': 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'fieldName': '_image',
|
||||||
|
'label': 'Image',
|
||||||
|
'fieldType': 'IMAGE_URI',
|
||||||
|
'editable': 'true',
|
||||||
|
'displayOrder': 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
with get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
for field in default_fields:
|
||||||
|
cursor.execute('''
|
||||||
|
INSERT INTO custom_ar_fields
|
||||||
|
(tenant_id, field_name, label, field_type, editable, display_order)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
''', (
|
||||||
|
tenant_id,
|
||||||
|
field['fieldName'],
|
||||||
|
field['label'],
|
||||||
|
field['fieldType'],
|
||||||
|
field['editable'],
|
||||||
|
field['displayOrder']
|
||||||
|
))
|
||||||
|
conn.commit()
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ def get_db():
|
|||||||
db_path = current_app.config['DATABASE_PATH']
|
db_path = current_app.config['DATABASE_PATH']
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
|
# Enable foreign key constraints
|
||||||
|
conn.execute('PRAGMA foreign_keys = ON')
|
||||||
try:
|
try:
|
||||||
yield conn
|
yield conn
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -10,6 +10,21 @@
|
|||||||
<h2>Add New Product</h2>
|
<h2>Add New Product</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
{% if not custom_fields %}
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<h4 class="alert-heading"><i class="bi bi-exclamation-triangle-fill me-2"></i>No Fields Configured</h4>
|
||||||
|
<p>Unable to add products. You must configure custom AR fields before adding products.</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">
|
||||||
|
<a href="/{{ tenant_id }}/ar_fields" class="btn btn-primary">
|
||||||
|
<i class="bi bi-gear-fill me-1"></i>Configure AR Fields
|
||||||
|
</a>
|
||||||
|
<a href="/{{ tenant_id }}/" class="btn btn-secondary ms-2">
|
||||||
|
<i class="bi bi-arrow-left me-1"></i>Back to Products
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
<form action="/{{ tenant_id }}/add" method="POST" enctype="multipart/form-data">
|
<form action="/{{ tenant_id }}/add" method="POST" enctype="multipart/form-data">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="product_id" class="form-label">Product ID *</label>
|
<label for="product_id" class="form-label">Product ID *</label>
|
||||||
@@ -71,6 +86,7 @@
|
|||||||
<button type="submit" class="btn btn-primary">Add Product</button>
|
<button type="submit" class="btn btn-primary">Add Product</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,18 @@
|
|||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
{% if not custom_fields %}
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<h4 class="alert-heading"><i class="bi bi-exclamation-triangle-fill me-2"></i>No AR Fields Configured</h4>
|
||||||
|
<p>Before you can add products, you need to configure custom AR fields that define what information is returned for each product.</p>
|
||||||
|
<hr>
|
||||||
|
<p class="mb-0">
|
||||||
|
<a href="/{{ tenant.id }}/ar_fields" class="btn btn-primary">
|
||||||
|
<i class="bi bi-gear-fill me-1"></i>Configure AR Fields
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h2>Products</h2>
|
<h2>Products</h2>
|
||||||
<div>
|
<div>
|
||||||
@@ -91,6 +103,7 @@
|
|||||||
<i class="bi bi-info-circle"></i> No products available yet. Click "Add New Product" to get started.
|
<i class="bi bi-info-circle"></i> No products available yet. Click "Add New Product" to get started.
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
|||||||
@@ -160,6 +160,15 @@
|
|||||||
<input type="text" class="form-control" id="tenantId" placeholder="e.g., demo-company" required pattern="[a-zA-Z0-9_-]+">
|
<input type="text" class="form-control" id="tenantId" placeholder="e.g., demo-company" required pattern="[a-zA-Z0-9_-]+">
|
||||||
<small class="form-text text-muted">Only letters, numbers, hyphens, and underscores allowed</small>
|
<small class="form-text text-muted">Only letters, numbers, hyphens, and underscores allowed</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="createDefaultFields" checked>
|
||||||
|
<label class="form-check-label" for="createDefaultFields">
|
||||||
|
Create products with default fields
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">Adds: Product Name, Item ID, Sale Price, and Image fields</small>
|
||||||
|
</div>
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<i class="bi bi-info-circle"></i>
|
<i class="bi bi-info-circle"></i>
|
||||||
<small>Default credentials: <strong>admin</strong> / <strong>admin</strong></small>
|
<small>Default credentials: <strong>admin</strong> / <strong>admin</strong></small>
|
||||||
@@ -237,13 +246,18 @@
|
|||||||
document.getElementById('newTenantForm').addEventListener('submit', function(e) {
|
document.getElementById('newTenantForm').addEventListener('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const tenantId = document.getElementById('tenantId').value;
|
const tenantId = document.getElementById('tenantId').value;
|
||||||
|
const createDefaults = document.getElementById('createDefaultFields').checked;
|
||||||
|
|
||||||
if (tenantId) {
|
if (tenantId) {
|
||||||
const normalizedId = tenantId.toLowerCase();
|
const normalizedId = tenantId.toLowerCase();
|
||||||
if (reservedIds.includes(normalizedId)) {
|
if (reservedIds.includes(normalizedId)) {
|
||||||
alert(`"${tenantId}" is a reserved name and cannot be used as a tenant ID.`);
|
alert(`"${tenantId}" is a reserved name and cannot be used as a tenant ID.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.location.href = '/' + normalizedId + '/';
|
|
||||||
|
// Redirect with query parameter for default fields
|
||||||
|
const url = '/' + normalizedId + '/' + (createDefaults ? '?create_defaults=1' : '');
|
||||||
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user