updates
This commit is contained in:
38
app/core/auth.py
Normal file
38
app/core/auth.py
Normal file
@ -0,0 +1,38 @@
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from .. import db
|
||||
from .models import User
|
||||
|
||||
auth = Blueprint('auth', __name__)
|
||||
|
||||
@auth.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
user = User.query.filter_by(email=request.form['email']).first()
|
||||
if user and check_password_hash(user.password_hash, request.form['password']):
|
||||
login_user(user)
|
||||
return redirect(url_for('core.index'))
|
||||
flash('Invalid email or password.')
|
||||
return render_template('auth/login.html')
|
||||
|
||||
@auth.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
email = request.form['email']
|
||||
password = request.form['password']
|
||||
if User.query.filter_by(email=email).first():
|
||||
flash('Email already registered.')
|
||||
else:
|
||||
user = User(email=email, password_hash=generate_password_hash(password))
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
flash('Account created, please log in.')
|
||||
return redirect(url_for('auth.login'))
|
||||
return render_template('auth/register.html')
|
||||
|
||||
@auth.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
logout_user()
|
||||
return redirect(url_for('core.index'))
|
50
app/core/models.py
Normal file
50
app/core/models.py
Normal file
@ -0,0 +1,50 @@
|
||||
from flask_login import UserMixin
|
||||
from datetime import datetime
|
||||
from .. import db
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(128), nullable=False)
|
||||
role = db.Column(db.String(50), default='user')
|
||||
is_verified = db.Column(db.Boolean, default=False)
|
||||
excluded_from_analytics = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
# Optional: relationship to submissions
|
||||
submissions = db.relationship('Submission', backref='user', lazy=True)
|
||||
|
||||
|
||||
class Submission(db.Model):
|
||||
__tablename__ = 'submission'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id', name='fk_submission_user_id'),
|
||||
nullable=False
|
||||
)
|
||||
common_name = db.Column(db.String(120), nullable=False)
|
||||
scientific_name = db.Column(db.String(120))
|
||||
price = db.Column(db.Float, nullable=False)
|
||||
source = db.Column(db.String(120))
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
height = db.Column(db.Float)
|
||||
width = db.Column(db.Float)
|
||||
leaf_count = db.Column(db.Integer)
|
||||
potting_mix = db.Column(db.String(255))
|
||||
container_size = db.Column(db.String(120))
|
||||
health_status = db.Column(db.String(50))
|
||||
notes = db.Column(db.Text)
|
||||
plant_id = db.Column(db.Integer)
|
||||
images = db.relationship('SubmissionImage', backref='submission', lazy=True)
|
||||
|
||||
|
||||
class SubmissionImage(db.Model):
|
||||
__tablename__ = 'submission_images'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
submission_id = db.Column(db.Integer, db.ForeignKey('submission.id'), nullable=False)
|
||||
file_path = db.Column(db.String(255), nullable=False)
|
@ -1,7 +1,141 @@
|
||||
from flask import Blueprint
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash
|
||||
from datetime import datetime
|
||||
from flask_login import login_required, current_user
|
||||
from sqlalchemy import and_
|
||||
from markupsafe import escape
|
||||
from werkzeug.utils import secure_filename
|
||||
import os
|
||||
|
||||
from .. import db
|
||||
from .models import Submission, SubmissionImage
|
||||
|
||||
core = Blueprint('core', __name__)
|
||||
|
||||
UPLOAD_FOLDER = 'app/static/uploads'
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'}
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
@core.route('/')
|
||||
def index():
|
||||
return 'Welcome to the Plant Price Tracker!'
|
||||
submissions = Submission.query.order_by(Submission.timestamp.desc()).limit(6).all()
|
||||
use_placeholder = len(submissions) == 0
|
||||
return render_template('index.html', submissions=submissions, use_placeholder=use_placeholder, current_year=datetime.now().year)
|
||||
|
||||
@core.route('/dashboard')
|
||||
@login_required
|
||||
def user_dashboard():
|
||||
return render_template('dashboard.html', current_year=datetime.now().year)
|
||||
|
||||
@core.route('/admin')
|
||||
@login_required
|
||||
def admin_dashboard():
|
||||
if current_user.role != 'admin':
|
||||
return render_template('unauthorized.html'), 403
|
||||
return render_template('admin_dashboard.html', current_year=datetime.now().year)
|
||||
|
||||
@core.route('/search', methods=['GET', 'POST'])
|
||||
def search():
|
||||
query = escape(request.values.get('q', '').strip())
|
||||
source = escape(request.values.get('source', '').strip())
|
||||
min_price = request.values.get('min_price', type=float)
|
||||
max_price = request.values.get('max_price', type=float)
|
||||
from_date = request.values.get('from_date')
|
||||
to_date = request.values.get('to_date')
|
||||
|
||||
if request.method == 'POST' and request.is_json:
|
||||
removed = request.json.get('remove')
|
||||
if removed == "Name":
|
||||
query = ""
|
||||
elif removed == "Source":
|
||||
source = ""
|
||||
elif removed == "Min Price":
|
||||
min_price = None
|
||||
elif removed == "Max Price":
|
||||
max_price = None
|
||||
elif removed == "From":
|
||||
from_date = None
|
||||
elif removed == "To":
|
||||
to_date = None
|
||||
|
||||
filters = []
|
||||
filter_tags = {}
|
||||
|
||||
if query:
|
||||
filters.append(Submission.common_name.ilike(f"%{query}%"))
|
||||
filter_tags['Name'] = query
|
||||
if source:
|
||||
filters.append(Submission.source.ilike(f"%{source}%"))
|
||||
filter_tags['Source'] = source
|
||||
if min_price is not None:
|
||||
filters.append(Submission.price >= min_price)
|
||||
filter_tags['Min Price'] = f"${min_price:.2f}"
|
||||
if max_price is not None:
|
||||
filters.append(Submission.price <= max_price)
|
||||
filter_tags['Max Price'] = f"${max_price:.2f}"
|
||||
if from_date:
|
||||
filters.append(Submission.timestamp >= from_date)
|
||||
filter_tags['From'] = from_date
|
||||
if to_date:
|
||||
filters.append(Submission.timestamp <= to_date)
|
||||
filter_tags['To'] = to_date
|
||||
|
||||
results = Submission.query.filter(and_(*filters)).order_by(Submission.timestamp.desc()).limit(50).all()
|
||||
|
||||
if request.is_json:
|
||||
return jsonify({
|
||||
"results_html": render_template("search_results.html", results=results),
|
||||
"tags_html": render_template("search_tags.html", filter_tags=filter_tags)
|
||||
})
|
||||
|
||||
return render_template('search.html', results=results, filter_tags=filter_tags)
|
||||
|
||||
@core.route('/submit', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def submit():
|
||||
if request.method == 'POST':
|
||||
common_name = escape(request.form.get('common_name', '').strip())
|
||||
scientific_name = escape(request.form.get('scientific_name', '').strip())
|
||||
price = request.form.get('price', type=float)
|
||||
source = escape(request.form.get('source', '').strip())
|
||||
timestamp = request.form.get('timestamp') or datetime.utcnow()
|
||||
height = request.form.get('height', type=float)
|
||||
width = request.form.get('width', type=float)
|
||||
leaf_count = request.form.get('leaf_count', type=int)
|
||||
potting_mix = escape(request.form.get('potting_mix', '').strip())
|
||||
container_size = escape(request.form.get('container_size', '').strip())
|
||||
health_status = escape(request.form.get('health_status', '').strip())
|
||||
notes = escape(request.form.get('notes', '').strip())
|
||||
|
||||
new_submission = Submission(
|
||||
user_id=current_user.id,
|
||||
common_name=common_name,
|
||||
scientific_name=scientific_name,
|
||||
price=price,
|
||||
source=source,
|
||||
timestamp=timestamp,
|
||||
height=height,
|
||||
width=width,
|
||||
leaf_count=leaf_count,
|
||||
potting_mix=potting_mix,
|
||||
container_size=container_size,
|
||||
health_status=health_status,
|
||||
notes=notes,
|
||||
)
|
||||
db.session.add(new_submission)
|
||||
db.session.commit()
|
||||
|
||||
files = request.files.getlist('images')
|
||||
for f in files[:5]:
|
||||
if f and allowed_file(f.filename):
|
||||
filename = secure_filename(f.filename)
|
||||
save_path = os.path.join(UPLOAD_FOLDER, filename)
|
||||
f.save(save_path)
|
||||
db.session.add(SubmissionImage(submission_id=new_submission.id, file_path=save_path))
|
||||
|
||||
db.session.commit()
|
||||
flash("Submission received!", "success")
|
||||
return redirect(url_for('core.index'))
|
||||
|
||||
return render_template('submit.html')
|
||||
|
Reference in New Issue
Block a user