Added "Edit using CSV" feature
This commit is contained in:
parent
7bb5727cbf
commit
e43601a86c
@ -11,14 +11,14 @@ def confirm_save():
|
||||
# Check if assets data is present in the request
|
||||
if 'assets' not in request.form:
|
||||
flash("No assets data found in the request.", "error")
|
||||
return redirect(url_for('uploadcsv.upload_file'))
|
||||
return redirect(url_for('uploadcsv.import_from_csv'))
|
||||
|
||||
try:
|
||||
# Parse the JSON data from the form
|
||||
edited_assets = json.loads(request.form['assets'])
|
||||
except json.JSONDecodeError:
|
||||
flash("Invalid JSON data in the request.", "error")
|
||||
return redirect(url_for('uploadcsv.upload_file'))
|
||||
return redirect(url_for('uploadcsv.import_from_csv'))
|
||||
|
||||
# Validate each asset
|
||||
errors = []
|
||||
@ -31,14 +31,25 @@ def confirm_save():
|
||||
# If there are validation errors, flash them and redirect back to the preview page
|
||||
for error in errors:
|
||||
flash(error, "error")
|
||||
return redirect(url_for('uploadcsv.upload_file'))
|
||||
return redirect(url_for('uploadcsv.import_from_csv'))
|
||||
|
||||
# Save validated assets to the database
|
||||
for asset_data in edited_assets:
|
||||
asset = Asset(**{
|
||||
attrib.attrib_name: asset_data[attrib.attrib_name]
|
||||
for attrib in item_attributes
|
||||
})
|
||||
primary_attrib = next((attrib for attrib in item_attributes if attrib.primary), None)
|
||||
if not primary_attrib:
|
||||
flash("Primary attribute not defined in configuration.", "error")
|
||||
return redirect(url_for('uploadcsv.import_from_csv'))
|
||||
|
||||
primary_value = asset_data[primary_attrib.attrib_name]
|
||||
asset = Asset.query.filter_by(**{primary_attrib.attrib_name: primary_value}).first()
|
||||
|
||||
if asset:
|
||||
# Update existing asset
|
||||
for attrib in item_attributes:
|
||||
setattr(asset, attrib.attrib_name, asset_data[attrib.attrib_name])
|
||||
else:
|
||||
# Add new asset
|
||||
asset = Asset(**asset_data)
|
||||
db.session.add(asset)
|
||||
|
||||
try:
|
||||
@ -46,8 +57,10 @@ def confirm_save():
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
flash(f"Error saving data to the database: {str(e)}", "error")
|
||||
return redirect(url_for('uploadcsv.upload_file'))
|
||||
return redirect(url_for('uploadcsv.import_from_csv'))
|
||||
|
||||
# Clear session data after successful insertion
|
||||
session.pop('assets', None)
|
||||
session.pop('new_entries', None)
|
||||
session.pop('existing_entries', None)
|
||||
session.pop('invalid_entries', None)
|
||||
return redirect('/viewall')
|
131
routes/upload.py
131
routes/upload.py
@ -6,14 +6,56 @@ from config import item_attributes
|
||||
|
||||
upload_bp = Blueprint('uploadcsv', __name__)
|
||||
|
||||
@upload_bp.route('/uploadcsv', methods=['GET', 'POST'])
|
||||
def upload_file():
|
||||
# Check if primary attribute is defined
|
||||
def _process_csv_file(file, mode):
|
||||
"""
|
||||
Helper function to process CSV file and separate entries based on mode.
|
||||
mode: 'import' or 'edit'
|
||||
"""
|
||||
# Extract CSV data
|
||||
csvdata = get_csv_data(file)
|
||||
|
||||
# Validate each row of data
|
||||
errors = []
|
||||
for i, row in enumerate(csvdata, start=1):
|
||||
error = validate_values(row)
|
||||
if error:
|
||||
errors.append(f"Row {i}: {error}")
|
||||
|
||||
if errors:
|
||||
return None, errors
|
||||
|
||||
# Separate entries based on mode
|
||||
primary_attrib = next((attrib for attrib in item_attributes if attrib.primary), None)
|
||||
if not primary_attrib:
|
||||
flash("Primary attribute not defined in configuration.", "error")
|
||||
return redirect(request.url)
|
||||
return None, ["Primary attribute not defined in configuration."]
|
||||
|
||||
new_entries = []
|
||||
existing_entries = []
|
||||
invalid_entries = []
|
||||
|
||||
for row in csvdata:
|
||||
primary_value = row[primary_attrib.attrib_name]
|
||||
asset_exists = Asset.query.filter_by(**{primary_attrib.attrib_name: primary_value}).first()
|
||||
|
||||
if mode == 'import':
|
||||
if asset_exists:
|
||||
invalid_entries.append(row) # Existing entries are invalid for import
|
||||
else:
|
||||
new_entries.append(row)
|
||||
elif mode == 'edit':
|
||||
if asset_exists:
|
||||
existing_entries.append(row) # Existing entries are valid for edit
|
||||
else:
|
||||
invalid_entries.append(row) # Non-existing entries are invalid for edit
|
||||
|
||||
return {
|
||||
'new_entries': new_entries,
|
||||
'existing_entries': existing_entries,
|
||||
'invalid_entries': invalid_entries
|
||||
}, None
|
||||
|
||||
@upload_bp.route('/import_from_csv', methods=['GET', 'POST'])
|
||||
def import_from_csv():
|
||||
if request.method == 'POST':
|
||||
# Check if a file was uploaded
|
||||
if 'file' not in request.files:
|
||||
@ -27,48 +69,61 @@ def upload_file():
|
||||
flash("Please upload a valid CSV file.", "error")
|
||||
return redirect(request.url)
|
||||
|
||||
try:
|
||||
# Extract CSV data
|
||||
csvdata = get_csv_data(file)
|
||||
except Exception as e:
|
||||
flash(f"Error reading CSV file: {str(e)}", "error")
|
||||
return redirect(request.url)
|
||||
|
||||
# Validate each row of data
|
||||
errors = []
|
||||
for i, row in enumerate(csvdata, start=1):
|
||||
error = validate_values(row)
|
||||
if error:
|
||||
errors.append(f"Row {i}: {error}")
|
||||
|
||||
# Process the CSV file
|
||||
result, errors = _process_csv_file(file, mode='import')
|
||||
if errors:
|
||||
# Flash all errors and redirect back to the upload page
|
||||
for error in errors:
|
||||
flash(error, "error")
|
||||
return redirect(request.url)
|
||||
|
||||
# Separate new and existing assets
|
||||
new_assets = []
|
||||
existing_assets = []
|
||||
for row in csvdata:
|
||||
primary_value = row[primary_attrib.attrib_name]
|
||||
asset_exists = Asset.query.filter_by(**{primary_attrib.attrib_name: primary_value}).first()
|
||||
if asset_exists:
|
||||
existing_assets.append(row)
|
||||
else:
|
||||
new_assets.append(row)
|
||||
# Store entries in session for further processing
|
||||
session['new_entries'] = result['new_entries']
|
||||
session['invalid_entries'] = result['invalid_entries']
|
||||
|
||||
# Store new assets in session for further processing
|
||||
session['new_assets'] = new_assets
|
||||
session['existing_assets'] = existing_assets
|
||||
|
||||
# Redirect to preview page with the CSV data
|
||||
# Redirect to preview page
|
||||
return render_template(
|
||||
'csv_preview.html',
|
||||
new_assets=new_assets,
|
||||
existing_assets=existing_assets,
|
||||
new_entries=result['new_entries'],
|
||||
invalid_entries=result['invalid_entries'],
|
||||
item_attributes=item_attributes
|
||||
)
|
||||
|
||||
# Render the upload page for GET requests
|
||||
return render_template('upload.html')
|
||||
return render_template('upload.html', mode="addnew")
|
||||
|
||||
@upload_bp.route('/edit_using_csv', methods=['GET', 'POST'])
|
||||
def edit_using_csv():
|
||||
if request.method == 'POST':
|
||||
# Check if a file was uploaded
|
||||
if 'file' not in request.files:
|
||||
flash("No file uploaded.", "error")
|
||||
return redirect(request.url)
|
||||
|
||||
file = request.files['file']
|
||||
|
||||
# Check if the file is a CSV
|
||||
if file.filename == '' or not file.filename.endswith('.csv'):
|
||||
flash("Please upload a valid CSV file.", "error")
|
||||
return redirect(request.url)
|
||||
|
||||
# Process the CSV file
|
||||
result, errors = _process_csv_file(file, mode='edit')
|
||||
if errors:
|
||||
for error in errors:
|
||||
flash(error, "error")
|
||||
return redirect(request.url)
|
||||
|
||||
# Store entries in session for further processing
|
||||
session['existing_entries'] = result['existing_entries']
|
||||
session['invalid_entries'] = result['invalid_entries']
|
||||
|
||||
# Redirect to preview page
|
||||
return render_template(
|
||||
'csv_preview.html',
|
||||
existing_entries=result['existing_entries'],
|
||||
invalid_entries=result['invalid_entries'],
|
||||
item_attributes=item_attributes
|
||||
)
|
||||
|
||||
# Render the upload page for GET requests
|
||||
return render_template('upload.html', mode="update")
|
@ -3,8 +3,8 @@ function collectEditedData(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Extract headers (attribute names) from the table
|
||||
const headers = [...document.querySelectorAll('.table-new-assets thead th')].map(th => th.dataset.attrib);
|
||||
const rows = document.querySelectorAll('.table-new-assets tbody tr');
|
||||
const headers = [...document.querySelectorAll('.table-valid-entries thead th')].map(th => th.dataset.attrib);
|
||||
const rows = document.querySelectorAll('.table-valid-entries tbody tr');
|
||||
const assets = [];
|
||||
|
||||
// Iterate through rows and collect data
|
||||
|
@ -23,27 +23,28 @@
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<!-- Render tables for new and existing assets -->
|
||||
{% for table_name, assets, editable, table_class in [
|
||||
('New Assets', new_assets, true, 'table-new-assets'),
|
||||
('Existing Assets', existing_assets, false, 'table-existing-assets')
|
||||
<!-- Render tables for new, existing, and invalid entries -->
|
||||
{% for table_name, entries, editable, tableclass in [
|
||||
('New Entries', new_entries, true, 'table-valid-entries'),
|
||||
('Existing Entries', existing_entries, true, 'table-valid-entries'),
|
||||
('Invalid Entries', invalid_entries, false, 'table-invalid-entries')
|
||||
] %}
|
||||
{% if assets %}
|
||||
{% if entries %}
|
||||
<h2>{{ table_name }}</h2>
|
||||
<table border="1" class="{{ table_class }}">
|
||||
<table border="1" class="{{ tableclass }}">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for attrib in item_attributes %}
|
||||
<th data-attrib="{{ attrib.attrib_name }}">{{ attrib.display_name }}</th>
|
||||
<th>{{ attrib.display_name }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for asset in assets %}
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
{% for attrib in item_attributes %}
|
||||
<td {% if editable %}contenteditable="true"{% endif %}>
|
||||
{{ asset[attrib.attrib_name] }}
|
||||
{{ entry[attrib.attrib_name] }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
@ -54,7 +55,7 @@
|
||||
{% endfor %}
|
||||
|
||||
<!-- Form button to confirm and save data -->
|
||||
{% if new_assets %}
|
||||
{% if new_entries or existing_entries %}
|
||||
<form action="/confirm_save" method="POST" onsubmit="return collectEditedData(event)">
|
||||
<button type="submit">Confirm and Save to Database</button>
|
||||
</form>
|
||||
|
@ -23,7 +23,13 @@
|
||||
{% endwith %}
|
||||
|
||||
<!-- Upload form -->
|
||||
<form action="/uploadcsv" method="POST" enctype="multipart/form-data">
|
||||
<form method="POST" enctype="multipart/form-data"
|
||||
{% if mode == "addnew" %}
|
||||
action="/import_from_csv"
|
||||
{% elif mode == "addnew" %}
|
||||
action="/edit_using_csv"
|
||||
{% endif %}
|
||||
>
|
||||
<label for="file">Choose a CSV file:</label>
|
||||
<input type="file" id="file" name="file" accept=".csv" required>
|
||||
<br><br>
|
||||
|
@ -40,9 +40,12 @@
|
||||
<form action="/create" method="get">
|
||||
<button type="submit">Add new Item</button>
|
||||
</form>
|
||||
<form action="/uploadcsv" method="get">
|
||||
<form action="/import_from_csv" method="get">
|
||||
<button type="submit">Import from CSV</button>
|
||||
</form>
|
||||
<form action="/edit_using_csv" method="get">
|
||||
<button type="submit">Edit using CSV</button>
|
||||
</form>
|
||||
<form action="/export_csv" method="POST">
|
||||
<button type="submit">Export Data</button>
|
||||
</form>
|
||||
|
Loading…
Reference in New Issue
Block a user