from flask import Blueprint, render_template, request, redirect, url_for, jsonify from flask_login import login_required, current_user from app import db from sqlalchemy import or_ from plugins.plant.models import Plant, PlantCommonName, PlantScientificName, Tag from flask_wtf import FlaskForm from wtforms import StringField, SelectMultipleField, SubmitField from wtforms.validators import Optional, Length, Regexp bp = Blueprint( 'search', __name__, url_prefix='/search', template_folder='templates/search' ) class SearchForm(FlaskForm): query = StringField( 'Search', validators=[ Optional(), Length(min=2, max=100, message="Search term must be between 2 and 100 characters."), Regexp(r'^[\w\s\-]+$', message="Search can only include letters, numbers, spaces, and dashes.") ] ) tags = SelectMultipleField('Tags', coerce=int) submit = SubmitField('Search') @bp.route('', methods=['GET', 'POST']) @bp.route('/', methods=['GET', 'POST']) @login_required def search(): form = SearchForm() form.tags.choices = [(t.id, t.name) for t in Tag.query.order_by(Tag.name).all()] if form.validate_on_submit(): q = form.query.data or '' selected = form.tags.data or [] tags_param = ','.join(map(str, selected)) if selected else '' return redirect(url_for('search.results', q=q, tags=tags_param)) return render_template('search/search.html', form=form) @bp.route('/results', methods=['GET']) @login_required def results(): q = request.args.get('q', '').strip() tags_param = request.args.get('tags', '') tags = [int(t) for t in tags_param.split(',') if t] if tags_param else [] like_term = f"%{q}%" # Base query, joining name tables for text search query = ( db.session.query(Plant) .join(Plant.common_name) .join(Plant.scientific_name) ) if q: query = query.filter( or_( PlantCommonName.name.ilike(like_term), PlantScientificName.name.ilike(like_term), Plant.plant_type.ilike(like_term), ) ) if tags: query = query.filter(Plant.tags.any(Tag.id.in_(tags))) # Only include active plants… query = query.filter(Plant.is_active.is_(True)) # …and either public plants or those owned by the current user query = query.filter( or_( Plant.is_public.is_(True), Plant.owner_id == current_user.id ) ) results = query.all() return render_template( 'search/results.html', results=results, query=q, tags=tags ) @bp.route('/tags') @login_required def search_tags(): term = request.args.get('term', '') like_term = f"%{term}%" matches = Tag.query.filter(Tag.name.ilike(like_term)).limit(10).all() return jsonify([t.name for t in matches])