""" this file contains the router logic regarding creating, deleting, duplicating, publishing and viewing forms """ from fastapi import ( APIRouter, Request, Depends, Form, ) from fastapi import status as http_status from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.templating import Jinja2Templates from sqlalchemy.orm import Session from sqlalchemy import func # import database connection and form and field models from ..db.db_connect import get_db from ..models.form_templates import FormTemplate, FormTemplateStatus from ..models.form_fields import FormField, FieldType router = APIRouter() templates = Jinja2Templates(directory="app/frontend/templates") #route to the form menu page @router.get("/form_menu", response_class=HTMLResponse) def form_menu_page(request: Request, db: Session = Depends(get_db)):# #query db for existing form templates try: form_templates = ( db.query(FormTemplate) .order_by(FormTemplate.updated_at.desc()) .all() ) except Exception: form_templates = [] # render page with context return templates.TemplateResponse( "forms/form_menu.html", { "request": request, "active": "form_menu", "templates": form_templates, "user": request.session.get("user"), }, ) # router to the form create page @router.get("/form_create", response_class=HTMLResponse) def form_create_page(request: Request): return templates.TemplateResponse( "forms/form_create.html", {"request": request, "active": "form_create"} ) # route to create a new blank form template used in the form create route @router.post("/form_templates") def create_form_template_post( request: Request, name: str = Form(...), status: str = Form("draft"), description: str | None = Form(None), db: Session = Depends(get_db), ): #created by current user session session_user = request.session.get("user") or {} created_by = session_user.get("id") #input of template name, default status is draft #description is optional name = (name or "").strip() status_value = (status or "draft").strip() or "draft" description = (description or "").strip() or None # if name input is missing redirect to form create page if not name: return RedirectResponse("/form_create", status_code=http_status.HTTP_303_SEE_OTHER) try: status_enum = FormTemplateStatus(status_value) except ValueError: status_enum = FormTemplateStatus.DRAFT #store inputted data in template variable template = FormTemplate( name=name, description=description, status=status_enum, created_by=created_by, ) #add to database db.add(template) try: db.commit() #error handling except Exception: db.rollback() return RedirectResponse("/form_create", status_code=http_status.HTTP_303_SEE_OTHER) # if successful redirect to the edit page of the newly created form template return RedirectResponse(f"/form_templates/{template.id}", status_code=http_status.HTTP_303_SEE_OTHER) #route for executing the create form template function for compatibility @router.post("/form_create") # define arguments for compatible form creation def create_form_template_compat( request: Request, name: str = Form(...), status: str = Form("draft"), description: str | None = Form(None), db: Session = Depends(get_db), ): # call the function defined in the create form template post route with the compatible form arguments return create_form_template_post( request=request, name=name, status=status, description=description, db=db, ) #route for editing specific form templates @router.get("/form_templates/{template_id}", response_class=HTMLResponse) def edit_form_template_page(template_id: int, request: Request, db: Session = Depends(get_db)): # fetch the form template using id template = db.query(FormTemplate).filter(FormTemplate.id == template_id).first() # create empty fields list fields = [] # if the template exists in the database query its associated fields if template: fields = ( db.query(FormField) .filter(FormField.template_id == template.id) #order by position .order_by(FormField.position.asc(), FormField.id.asc()) .all() ) # return the form edit page with the existing template and its fields return templates.TemplateResponse( "forms/form_edit.html", { "request": request, "active": "form_menu", "template": template, "fields": fields, "user": request.session.get("user"), }, ) # route to form preview page @router.get("/form_templates/{template_id}/preview", response_class=HTMLResponse) def preview_form_template_page(template_id: int, request: Request, db: Session = Depends(get_db)): # query template by id template = db.query(FormTemplate).filter(FormTemplate.id == template_id).first() fields = [] #query associated fields if template exists if template: fields = ( db.query(FormField) .filter(FormField.template_id == template.id) .order_by(FormField.position.asc(), FormField.id.asc()) .all() ) # return form preview html populated with context return templates.TemplateResponse( "forms/from_preview.html", { "request": request, "active": "form_menu", "template": template, "fields": fields, "user": request.session.get("user"), }, ) # route to defining template duplication logic @router.post("/form_templates/{template_id}/duplicate") def duplicate_form_template(template_id: int, request: Request, db: Session = Depends(get_db)): #query template by id template = db.query(FormTemplate).filter(FormTemplate.id == template_id).first() #error handling for missing template if not template: return RedirectResponse("/form_menu", status_code=http_status.HTTP_303_SEE_OTHER) #define user creating the duplicate from session session_user = request.session.get("user") or {} created_by = session_user.get("id") #define new name for duplicated template base_name = (template.name or "Untitled").strip() #append (copy) to basename new_name = f"{base_name} (Copy)" # define new template variable with arguments new_template = FormTemplate( name=new_name, description=template.description, status=FormTemplateStatus.DRAFT, created_by=created_by, ) #add new template to database db.add(new_template) #flush to get new template id db.flush() # define fields variable querying the existing fields from the original template fields = ( db.query(FormField) .filter(FormField.template_id == template.id) .order_by(FormField.position.asc(), FormField.id.asc()) .all() ) # duplicate each field for the new template for field in fields: duplicate = FormField( template_id=new_template.id, question_text=fields.question_text, type=fields.type, required=fields.required, position=fields.position, ) db.add(duplicate) try: db.commit() #error handling except Exception: db.rollback() return RedirectResponse("/form_menu", status_code=http_status.HTTP_303_SEE_OTHER) # redirect to the edit page of the newly duplicated template return RedirectResponse(f"/form_templates/{new_template.id}", status_code=http_status.HTTP_303_SEE_OTHER) #router defining the toggle publish/draft logic for a template @router.post("/form_templates/{template_id}/toggle_publish") def toggle_publish_form_template(template_id: int, request: Request, db: Session = Depends(get_db)): #query template by id template = db.query(FormTemplate).filter(FormTemplate.id == template_id).first() #error handling for missing template if not template: return RedirectResponse("/form_menu", status_code=http_status.HTTP_303_SEE_OTHER) #toggle status between published and draft try: template.status = ( FormTemplateStatus.PUBLISHED if template.status != FormTemplateStatus.PUBLISHED else FormTemplateStatus.DRAFT ) db.add(template) db.commit() #error handling except Exception: db.rollback() return RedirectResponse("/form_menu", status_code=http_status.HTTP_303_SEE_OTHER) # router defining the template deletion logic @router.post("/form_templates/{template_id}/delete") def delete_form_template(template_id: int, db: Session = Depends(get_db)): # query template by id template = db.query(FormTemplate).filter(FormTemplate.id == template_id).first() # error handling for missing template if not template: return RedirectResponse("/form_menu", status_code=http_status.HTTP_303_SEE_OTHER) # delete associated fields and the template itself try: db.query(FormField).filter(FormField.template_id == template.id).delete(synchronize_session=False) db.delete(template) db.commit() #error handling except Exception: db.rollback() # redirect to form menu after deletion return RedirectResponse("/form_menu", status_code=http_status.HTTP_303_SEE_OTHER)