csv upload
This commit is contained in:
156
app.py
156
app.py
@@ -1,4 +1,4 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify
|
||||
from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify, Response
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from datetime import datetime
|
||||
@@ -7,6 +7,8 @@ from werkzeug.utils import secure_filename
|
||||
import os
|
||||
from config import Config
|
||||
import random
|
||||
import csv
|
||||
import io
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
@@ -1453,6 +1455,158 @@ def recognize_plant():
|
||||
random_plant = random.choice(plants)
|
||||
return jsonify({'redirect': url_for('plant', plant_id=random_plant.id)})
|
||||
|
||||
@app.route('/admin/plants/upload-csv', methods=['POST'])
|
||||
def upload_plants_csv():
|
||||
if not is_logged_in():
|
||||
return redirect(url_for('login'))
|
||||
|
||||
if 'csv_file' not in request.files:
|
||||
flash('No file uploaded', 'error')
|
||||
return redirect(url_for('manage_plants'))
|
||||
|
||||
file = request.files['csv_file']
|
||||
if file.filename == '':
|
||||
flash('No file selected', 'error')
|
||||
return redirect(url_for('manage_plants'))
|
||||
|
||||
if not file.filename.endswith('.csv'):
|
||||
flash('Please upload a CSV file', 'error')
|
||||
return redirect(url_for('manage_plants'))
|
||||
|
||||
try:
|
||||
# Read the CSV file
|
||||
stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
|
||||
csv_reader = csv.DictReader(stream)
|
||||
|
||||
# Get all the lookup dictionaries
|
||||
climates = {c.name.lower(): c.id for c in Climate.query.all()}
|
||||
environments = {e.name.lower(): e.id for e in Environment.query.all()}
|
||||
lights = {l.name.lower(): l.id for l in Light.query.all()}
|
||||
toxicities = {t.name.lower(): t.id for t in Toxicity.query.all()}
|
||||
sizes = {s.name.lower(): s.id for s in Size.query.all()}
|
||||
care_difficulties = {d.name.lower(): d.id for d in CareDifficulty.query.all()}
|
||||
growth_rates = {r.name.lower(): r.id for r in GrowthRate.query.all()}
|
||||
products = {p.name.lower(): p.id for p in Product.query.all()}
|
||||
|
||||
success_count = 0
|
||||
error_count = 0
|
||||
duplicate_count = 0
|
||||
|
||||
for row in csv_reader:
|
||||
try:
|
||||
# Check if plant with this name already exists
|
||||
existing_plant = Plant.query.filter_by(name=row['name']).first()
|
||||
if existing_plant:
|
||||
duplicate_count += 1
|
||||
continue
|
||||
|
||||
# Create new plant
|
||||
plant = Plant(
|
||||
name=row['name'],
|
||||
description=row.get('description', ''),
|
||||
care_guide=row.get('care_guide', ''),
|
||||
date_added=datetime.utcnow()
|
||||
)
|
||||
|
||||
# Set relationships using the lookup dictionaries
|
||||
if row.get('climate'):
|
||||
plant.climate_id = climates.get(row['climate'].lower())
|
||||
if row.get('environment'):
|
||||
plant.environment_id = environments.get(row['environment'].lower())
|
||||
if row.get('light'):
|
||||
plant.light_id = lights.get(row['light'].lower())
|
||||
if row.get('toxicity'):
|
||||
plant.toxicity_id = toxicities.get(row['toxicity'].lower())
|
||||
if row.get('size'):
|
||||
plant.size_id = sizes.get(row['size'].lower())
|
||||
if row.get('care_difficulty'):
|
||||
plant.care_difficulty_id = care_difficulties.get(row['care_difficulty'].lower())
|
||||
if row.get('growth_rate'):
|
||||
plant.growth_rate_id = growth_rates.get(row['growth_rate'].lower())
|
||||
|
||||
# Handle products (comma-separated list)
|
||||
if row.get('products'):
|
||||
product_ids = []
|
||||
for product_name in row['products'].split(','):
|
||||
product_name = product_name.strip().lower()
|
||||
if product_name in products:
|
||||
product_ids.append(str(products[product_name]))
|
||||
plant.products = ','.join(product_ids)
|
||||
|
||||
db.session.add(plant)
|
||||
success_count += 1
|
||||
|
||||
except Exception as e:
|
||||
error_count += 1
|
||||
print(f"Error processing row: {row}, Error: {str(e)}")
|
||||
continue
|
||||
|
||||
db.session.commit()
|
||||
message = f'Successfully imported {success_count} plants.'
|
||||
if duplicate_count > 0:
|
||||
message += f' {duplicate_count} duplicate plants were skipped.'
|
||||
if error_count > 0:
|
||||
message += f' {error_count} errors occurred.'
|
||||
flash(message, 'success')
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error processing CSV file: {str(e)}', 'error')
|
||||
db.session.rollback()
|
||||
|
||||
return redirect(url_for('manage_plants'))
|
||||
|
||||
@app.route('/admin/plants/download-template')
|
||||
def download_plants_template():
|
||||
if not is_logged_in():
|
||||
return redirect(url_for('login'))
|
||||
|
||||
# Create a CSV template with headers and example data
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
|
||||
# Write headers
|
||||
headers = [
|
||||
'name',
|
||||
'description',
|
||||
'care_guide',
|
||||
'climate',
|
||||
'environment',
|
||||
'light',
|
||||
'toxicity',
|
||||
'size',
|
||||
'care_difficulty',
|
||||
'growth_rate',
|
||||
'products'
|
||||
]
|
||||
writer.writerow(headers)
|
||||
|
||||
# Write example row with available options
|
||||
example_row = [
|
||||
'Monstera Deliciosa', # name
|
||||
'Beautiful tropical plant with distinctive leaf holes', # description
|
||||
'Water weekly, mist leaves occasionally', # care_guide
|
||||
', '.join([c.name for c in Climate.query.all()]), # climate options
|
||||
', '.join([e.name for e in Environment.query.all()]), # environment options
|
||||
', '.join([l.name for l in Light.query.all()]), # light options
|
||||
', '.join([t.name for t in Toxicity.query.all()]), # toxicity options
|
||||
', '.join([s.name for s in Size.query.all()]), # size options
|
||||
', '.join([d.name for d in CareDifficulty.query.all()]), # care_difficulty options
|
||||
', '.join([r.name for r in GrowthRate.query.all()]), # growth_rate options
|
||||
', '.join([p.name for p in Product.query.all()]) # product options
|
||||
]
|
||||
writer.writerow(example_row)
|
||||
|
||||
# Create the response
|
||||
output.seek(0)
|
||||
return Response(
|
||||
output,
|
||||
mimetype='text/csv',
|
||||
headers={
|
||||
'Content-Disposition': 'attachment; filename=plants_template.csv',
|
||||
'Content-Type': 'text/csv',
|
||||
}
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
with app.app_context():
|
||||
db.create_all() # Create all tables
|
||||
|
||||
@@ -6,7 +6,18 @@
|
||||
<div class="bg-white rounded-lg shadow p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-2xl font-bold">All Plants</h2>
|
||||
<button id="show-add-plant" class="btn-main px-6 py-2 font-semibold">Add Plant</button>
|
||||
<div class="flex gap-4">
|
||||
<a href="{{ url_for('download_plants_template') }}" class="btn-secondary px-6 py-2 font-semibold flex items-center gap-2">
|
||||
<i class="fas fa-file-download"></i> Download Template
|
||||
</a>
|
||||
<form id="csv-upload-form" method="POST" action="{{ url_for('upload_plants_csv') }}" enctype="multipart/form-data" class="inline">
|
||||
<input type="file" name="csv_file" id="csv_file" accept=".csv" class="hidden" onchange="this.form.submit()">
|
||||
<button type="button" onclick="document.getElementById('csv_file').click()" class="btn-secondary px-6 py-2 font-semibold flex items-center gap-2">
|
||||
<i class="fas fa-file-import"></i> Import CSV
|
||||
</button>
|
||||
</form>
|
||||
<button id="show-add-plant" class="btn-main px-6 py-2 font-semibold">Add Plant</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="add-plant-form-card" class="hidden mb-8">
|
||||
<div class="bg-gray-50 rounded-lg shadow p-6 max-w-2xl mx-auto" style="overflow:visible;">
|
||||
|
||||
Reference in New Issue
Block a user