mirror of
https://github.com/sstent/foodplanner.git
synced 2026-03-18 03:05:24 +00:00
fixing imports
This commit is contained in:
9
.github/workflows/build-and-push.yml
vendored
9
.github/workflows/build-and-push.yml
vendored
@@ -35,6 +35,15 @@ jobs:
|
|||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache # Or a custom path for your cache
|
||||||
|
key: ${{ runner.os }}-docker-${{ hashFiles('Dockerfile') }}-${{ hashFiles('src/**') }} # Example key
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-docker-${{ hashFiles('Dockerfile') }}-
|
||||||
|
${{ runner.os }}-docker-
|
||||||
|
|
||||||
- name: Extract metadata for Docker
|
- name: Extract metadata for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
|
|||||||
77
main.py
77
main.py
@@ -1938,7 +1938,6 @@ async def tracker_save_template(request: Request, db: Session = Depends(get_db))
|
|||||||
meal_time=tracked_meal.meal_time
|
meal_time=tracked_meal.meal_time
|
||||||
)
|
)
|
||||||
db.add(template_meal)
|
db.add(template_meal)
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
logging.info(f"DEBUG: Successfully saved template with {len(tracked_meals)} meals")
|
logging.info(f"DEBUG: Successfully saved template with {len(tracked_meals)} meals")
|
||||||
@@ -2060,4 +2059,78 @@ async def tracker_reset_to_plan(request: Request, db: Session = Depends(get_db))
|
|||||||
@app.get("/test")
|
@app.get("/test")
|
||||||
async def test_route():
|
async def test_route():
|
||||||
logging.info("DEBUG: Test route called")
|
logging.info("DEBUG: Test route called")
|
||||||
return {"status": "success", "message": "Test route is working"}
|
return {"status": "success", "message": "Test route is working"}
|
||||||
|
|
||||||
|
@app.post("/templates/upload")
|
||||||
|
async def bulk_upload_templates(file: UploadFile = File(...), db: Session = Depends(get_db)):
|
||||||
|
"""Handle bulk template upload from CSV"""
|
||||||
|
try:
|
||||||
|
contents = await file.read()
|
||||||
|
decoded = contents.decode('utf-8').splitlines()
|
||||||
|
reader = csv.DictReader(decoded)
|
||||||
|
|
||||||
|
stats = {'created': 0, 'updated': 0, 'errors': []}
|
||||||
|
|
||||||
|
for row_num, row in enumerate(reader, 2): # Row numbers start at 2 (1-based + header)
|
||||||
|
try:
|
||||||
|
user = row.get('User', '').strip()
|
||||||
|
template_id = row.get('ID', '').strip()
|
||||||
|
|
||||||
|
if not user or not template_id:
|
||||||
|
stats['errors'].append(f"Row {row_num}: Missing User or ID")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create template name in format <User>-<ID>
|
||||||
|
template_name = f"{user}-{template_id}"
|
||||||
|
|
||||||
|
# Check if template already exists
|
||||||
|
existing_template = db.query(Template).filter(Template.name == template_name).first()
|
||||||
|
if existing_template:
|
||||||
|
# Update existing template - remove existing meals
|
||||||
|
db.query(TemplateMeal).filter(TemplateMeal.template_id == existing_template.id).delete()
|
||||||
|
template = existing_template
|
||||||
|
stats['updated'] += 1
|
||||||
|
else:
|
||||||
|
# Create new template
|
||||||
|
template = Template(name=template_name)
|
||||||
|
db.add(template)
|
||||||
|
stats['created'] += 1
|
||||||
|
|
||||||
|
db.flush() # Get template ID
|
||||||
|
|
||||||
|
# Meal time mappings from CSV columns
|
||||||
|
meal_columns = {
|
||||||
|
'Beverage 1': 'Beverage 1',
|
||||||
|
'Breakfast': 'Breakfast',
|
||||||
|
'Lunch': 'Lunch',
|
||||||
|
'Dinner': 'Dinner',
|
||||||
|
'Snack 1': 'Snack 1',
|
||||||
|
'Snack 2': 'Snack 2'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process each meal column
|
||||||
|
for csv_column, meal_time in meal_columns.items():
|
||||||
|
meal_name = row.get(csv_column, '').strip()
|
||||||
|
if meal_name:
|
||||||
|
# Find meal by name
|
||||||
|
meal = db.query(Meal).filter(Meal.name.ilike(meal_name)).first()
|
||||||
|
if meal:
|
||||||
|
# Create template meal
|
||||||
|
template_meal = TemplateMeal(
|
||||||
|
template_id=template.id,
|
||||||
|
meal_id=meal.id,
|
||||||
|
meal_time=meal_time
|
||||||
|
)
|
||||||
|
db.add(template_meal)
|
||||||
|
else:
|
||||||
|
stats['errors'].append(f"Row {row_num}: Meal '{meal_name}' not found for {meal_time}")
|
||||||
|
|
||||||
|
except (KeyError, ValueError) as e:
|
||||||
|
stats['errors'].append(f"Row {row_num}: {str(e)}")
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
return stats
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
db.rollback()
|
||||||
|
return {"status": "error", "message": str(e)}
|
||||||
@@ -32,4 +32,120 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mt-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Food Import</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="/foods/upload" method="post" enctype="multipart/form-data" class="csv-upload-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">CSV File</label>
|
||||||
|
<input type="file" class="form-control" name="file" accept=".csv" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-secondary">Upload Foods CSV</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Meal Import</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="/meals/upload" method="post" enctype="multipart/form-data" class="csv-upload-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">CSV File</label>
|
||||||
|
<input type="file" class="form-control" name="file" accept=".csv" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-secondary">Upload Meals CSV</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="mb-0">Template Import</h5>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form action="/templates/upload" method="post" enctype="multipart/form-data" class="csv-upload-form">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">CSV File</label>
|
||||||
|
<input type="file" class="form-control" name="file" accept=".csv" required>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-secondary">Upload Templates CSV</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4" id="upload-results" style="display: none;">
|
||||||
|
<div class="alert">
|
||||||
|
<strong>Upload Results:</strong>
|
||||||
|
<p>Created: <span id="created-count"></span></p>
|
||||||
|
<p>Updated: <span id="updated-count"></span></p>
|
||||||
|
<div id="error-list" class="mt-2 text-danger"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.querySelectorAll('.csv-upload-form').forEach(form => {
|
||||||
|
form.addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
const resultsDiv = document.getElementById('upload-results');
|
||||||
|
const alertDiv = resultsDiv.querySelector('.alert');
|
||||||
|
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status"></span> Uploading...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formData = new FormData(form);
|
||||||
|
const response = await fetch(form.action, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
|
||||||
|
const results = await response.json();
|
||||||
|
resultsDiv.style.display = 'block';
|
||||||
|
|
||||||
|
if (results.status === 'error') {
|
||||||
|
alertDiv.className = 'alert alert-danger';
|
||||||
|
document.getElementById('created-count').textContent = 'N/A';
|
||||||
|
document.getElementById('updated-count').textContent = 'N/A';
|
||||||
|
document.getElementById('error-list').innerHTML = `<strong>Error:</strong> ${results.message}`;
|
||||||
|
} else {
|
||||||
|
alertDiv.className = 'alert alert-success';
|
||||||
|
document.getElementById('created-count').textContent = results.created || 0;
|
||||||
|
document.getElementById('updated-count').textContent = results.updated || 0;
|
||||||
|
|
||||||
|
if (results.errors?.length > 0) {
|
||||||
|
document.getElementById('error-list').innerHTML =
|
||||||
|
`<strong>Errors (${results.errors.length}):</strong><br>` + results.errors.join('<br>');
|
||||||
|
} else {
|
||||||
|
document.getElementById('error-list').innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
resultsDiv.style.display = 'block';
|
||||||
|
alertDiv.className = 'alert alert-danger';
|
||||||
|
document.getElementById('created-count').textContent = 'N/A';
|
||||||
|
document.getElementById('updated-count').textContent = 'N/A';
|
||||||
|
document.getElementById('error-list').innerHTML =
|
||||||
|
`<strong>Upload Failed:</strong> ${error.message}`;
|
||||||
|
} finally {
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.textContent = 'Upload CSV';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user