Added "Edit using CSV" feature

This commit is contained in:
Candifloss 2025-03-06 11:39:41 +05:30
parent 7bb5727cbf
commit e43601a86c
6 changed files with 140 additions and 62 deletions

View File

@ -11,14 +11,14 @@ def confirm_save():
# Check if assets data is present in the request # Check if assets data is present in the request
if 'assets' not in request.form: if 'assets' not in request.form:
flash("No assets data found in the request.", "error") 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: try:
# Parse the JSON data from the form # Parse the JSON data from the form
edited_assets = json.loads(request.form['assets']) edited_assets = json.loads(request.form['assets'])
except json.JSONDecodeError: except json.JSONDecodeError:
flash("Invalid JSON data in the request.", "error") 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 # Validate each asset
errors = [] errors = []
@ -31,23 +31,36 @@ def confirm_save():
# If there are validation errors, flash them and redirect back to the preview page # If there are validation errors, flash them and redirect back to the preview page
for error in errors: for error in errors:
flash(error, "error") flash(error, "error")
return redirect(url_for('uploadcsv.upload_file')) return redirect(url_for('uploadcsv.import_from_csv'))
# Save validated assets to the database # Save validated assets to the database
for asset_data in edited_assets: for asset_data in edited_assets:
asset = Asset(**{ primary_attrib = next((attrib for attrib in item_attributes if attrib.primary), None)
attrib.attrib_name: asset_data[attrib.attrib_name] if not primary_attrib:
for attrib in item_attributes flash("Primary attribute not defined in configuration.", "error")
}) return redirect(url_for('uploadcsv.import_from_csv'))
db.session.add(asset)
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: try:
db.session.commit() db.session.commit()
except Exception as e: except Exception as e:
db.session.rollback() db.session.rollback()
flash(f"Error saving data to the database: {str(e)}", "error") 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 # 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') return redirect('/viewall')

View File

@ -6,14 +6,56 @@ from config import item_attributes
upload_bp = Blueprint('uploadcsv', __name__) upload_bp = Blueprint('uploadcsv', __name__)
@upload_bp.route('/uploadcsv', methods=['GET', 'POST']) def _process_csv_file(file, mode):
def upload_file(): """
# Check if primary attribute is defined 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) primary_attrib = next((attrib for attrib in item_attributes if attrib.primary), None)
if not primary_attrib: if not primary_attrib:
flash("Primary attribute not defined in configuration.", "error") return None, ["Primary attribute not defined in configuration."]
return redirect(request.url)
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': if request.method == 'POST':
# Check if a file was uploaded # Check if a file was uploaded
if 'file' not in request.files: if 'file' not in request.files:
@ -27,48 +69,61 @@ def upload_file():
flash("Please upload a valid CSV file.", "error") flash("Please upload a valid CSV file.", "error")
return redirect(request.url) return redirect(request.url)
try: # Process the CSV file
# Extract CSV data result, errors = _process_csv_file(file, mode='import')
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}")
if errors: if errors:
# Flash all errors and redirect back to the upload page
for error in errors: for error in errors:
flash(error, "error") flash(error, "error")
return redirect(request.url) return redirect(request.url)
# Separate new and existing assets # Store entries in session for further processing
new_assets = [] session['new_entries'] = result['new_entries']
existing_assets = [] session['invalid_entries'] = result['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 asset_exists:
existing_assets.append(row)
else:
new_assets.append(row)
# Store new assets in session for further processing # Redirect to preview page
session['new_assets'] = new_assets
session['existing_assets'] = existing_assets
# Redirect to preview page with the CSV data
return render_template( return render_template(
'csv_preview.html', 'csv_preview.html',
new_assets=new_assets, new_entries=result['new_entries'],
existing_assets=existing_assets, invalid_entries=result['invalid_entries'],
item_attributes=item_attributes item_attributes=item_attributes
) )
# Render the upload page for GET requests # 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")

View File

@ -3,8 +3,8 @@ function collectEditedData(event) {
event.preventDefault(); event.preventDefault();
// Extract headers (attribute names) from the table // Extract headers (attribute names) from the table
const headers = [...document.querySelectorAll('.table-new-assets thead th')].map(th => th.dataset.attrib); const headers = [...document.querySelectorAll('.table-valid-entries thead th')].map(th => th.dataset.attrib);
const rows = document.querySelectorAll('.table-new-assets tbody tr'); const rows = document.querySelectorAll('.table-valid-entries tbody tr');
const assets = []; const assets = [];
// Iterate through rows and collect data // Iterate through rows and collect data

View File

@ -23,27 +23,28 @@
{% endif %} {% endif %}
{% endwith %} {% endwith %}
<!-- Render tables for new and existing assets --> <!-- Render tables for new, existing, and invalid entries -->
{% for table_name, assets, editable, table_class in [ {% for table_name, entries, editable, tableclass in [
('New Assets', new_assets, true, 'table-new-assets'), ('New Entries', new_entries, true, 'table-valid-entries'),
('Existing Assets', existing_assets, false, 'table-existing-assets') ('Existing Entries', existing_entries, true, 'table-valid-entries'),
('Invalid Entries', invalid_entries, false, 'table-invalid-entries')
] %} ] %}
{% if assets %} {% if entries %}
<h2>{{ table_name }}</h2> <h2>{{ table_name }}</h2>
<table border="1" class="{{ table_class }}"> <table border="1" class="{{ tableclass }}">
<thead> <thead>
<tr> <tr>
{% for attrib in item_attributes %} {% for attrib in item_attributes %}
<th data-attrib="{{ attrib.attrib_name }}">{{ attrib.display_name }}</th> <th>{{ attrib.display_name }}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for asset in assets %} {% for entry in entries %}
<tr> <tr>
{% for attrib in item_attributes %} {% for attrib in item_attributes %}
<td {% if editable %}contenteditable="true"{% endif %}> <td {% if editable %}contenteditable="true"{% endif %}>
{{ asset[attrib.attrib_name] }} {{ entry[attrib.attrib_name] }}
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
@ -54,7 +55,7 @@
{% endfor %} {% endfor %}
<!-- Form button to confirm and save data --> <!-- 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)"> <form action="/confirm_save" method="POST" onsubmit="return collectEditedData(event)">
<button type="submit">Confirm and Save to Database</button> <button type="submit">Confirm and Save to Database</button>
</form> </form>

View File

@ -23,7 +23,13 @@
{% endwith %} {% endwith %}
<!-- Upload form --> <!-- 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> <label for="file">Choose a CSV file:</label>
<input type="file" id="file" name="file" accept=".csv" required> <input type="file" id="file" name="file" accept=".csv" required>
<br><br> <br><br>

View File

@ -40,9 +40,12 @@
<form action="/create" method="get"> <form action="/create" method="get">
<button type="submit">Add new Item</button> <button type="submit">Add new Item</button>
</form> </form>
<form action="/uploadcsv" method="get"> <form action="/import_from_csv" method="get">
<button type="submit">Import from CSV</button> <button type="submit">Import from CSV</button>
</form> </form>
<form action="/edit_using_csv" method="get">
<button type="submit">Edit using CSV</button>
</form>
<form action="/export_csv" method="POST"> <form action="/export_csv" method="POST">
<button type="submit">Export Data</button> <button type="submit">Export Data</button>
</form> </form>