routes.py 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. from flask import render_template, request, session, jsonify, flash, make_response, redirect, url_for, abort, send_file, render_template_string
  2. from flask_login import login_required, current_user
  3. from app.main import bp
  4. from app.extensions import db, dict_row, dict_instance, extract_exc
  5. from app.models.users import tbl_users
  6. from app.models.cv_data import *
  7. from app.auth import check_admin, check_capture
  8. from sqlalchemy import insert, delete, update, select
  9. from sqlalchemy import and_, or_, not_, func
  10. import json, requests, re, os
  11. from .forms import RecordForm, QualificationTypeForm, QualificationForm, QualificationTypeRemovalForm, QualificationRemovalForm, RecordQualificationForm, RecordQualificationRemovalForm
  12. from .forms import RoleDepartmentForm, RoleForm, RoleDepartmentRemovalForm, RoleRemovalForm, \
  13. UploadForm, UploadRemovalForm, FilterForm, TenderForm, TenderRemovalForm, TenderAllocationForm, TenderCVRemovalForm
  14. from .forms import UserForm, UserRemovalForm
  15. import re
  16. import datetime as datetime_class
  17. from datetime import datetime, time, timezone, timedelta, date
  18. from app import Config
  19. from app.extensions import dict_instance, dict_row
  20. import io
  21. def end_user_debugging(s_additional:str = "", bl_html:bool = True):
  22. """just for initial testing"""
  23. d_exc = extract_exc(s_additional)
  24. s_dt = datetime.now().astimezone(timezone(timedelta(hours=2))).strftime("%Y-%m-%d %H:%M:%S")
  25. s_logging = f"<ul><li>date-time: {s_dt}</li>\n<li>line no.: {d_exc['i_lineno']}</li>\n<li>filename: {d_exc['s_filename']}</li>\n<li>except: {d_exc['s_except']}</li>\n<li>additional: {d_exc['s_additional']}</li>\n</ul>" if (isinstance(d_exc, dict) and bl_html) else f"date-time: {s_dt}\nindex-landing page\n" + str(d_exc["s_except"])
  26. return f"<!doctype html>\n<html lang=\"en\">\n<head>\n<title>Issue</title>\n</head>\n<body>\n<h2>Issue details</h2>\n{s_logging}\n</body>\n</html>"
  27. # end of end_user_debugging function
  28. @bp.route("/", methods=["GET", "POST"])
  29. @login_required
  30. def index():
  31. """landing page"""
  32. try:
  33. # filtering choices and current data
  34. filter_form = FilterForm()
  35. # populate some select choices
  36. l_role_departments = list(map(dict_row, db.session.query(tbl_role_departments.id, tbl_role_departments.v_department_name).order_by(tbl_role_departments.v_department_name).all()))
  37. for ix, d in enumerate(l_role_departments): l_role_departments[ix] = (d["id"], d["v_department_name"])
  38. filter_form.sel_role_department.choices = [(0, "---")] + l_role_departments
  39. # populate roles if department was selected
  40. i_department_id = request.form.get("sel_role_department", 0, type=int)
  41. if i_department_id>0:
  42. q_roles = db.session.query(tbl_roles.id, tbl_roles.v_role_name).filter(tbl_roles.i_department_id==i_department_id)
  43. if q_roles.count()>0:
  44. q_roles = q_roles.order_by(tbl_roles.v_role_name)
  45. l_roles = list(map(dict_row, q_roles.all()))
  46. for ix, d in enumerate(l_roles): l_roles[ix] = (d["id"], d["v_role_name"])
  47. filter_form.sel_role.choices = [(0, "---")] + l_roles
  48. # end of checking result count of query
  49. # end of checking if department submitted
  50. l_all_languages = list(map(dict_row, db.session.query(tbl_all_languages.v_language_abbreviation, tbl_all_languages.v_language_name).filter(tbl_all_languages.bl_south_african==True).order_by(tbl_all_languages.v_language_name).all()))
  51. for ix, d in enumerate(l_all_languages): l_all_languages[ix] = (d["v_language_abbreviation"], d["v_language_name"])
  52. filter_form.sel_language.choices = [("", "---")] + l_all_languages
  53. l_qualification_types = list(map(dict_row, db.session.query(tbl_qualification_types.id, tbl_qualification_types.v_qualification_type).order_by(tbl_qualification_types.v_qualification_type).all()))
  54. for ix, d in enumerate(l_qualification_types): l_qualification_types[ix] = (d["id"], d["v_qualification_type"])
  55. filter_form.sel_qualification_type_1.choices = [(0, "---")] + l_qualification_types
  56. filter_form.sel_qualification_type_2.choices = [(0, "---")] + l_qualification_types
  57. i_type_1 = request.form.get("sel_qualification_type_1", 0, type=int)
  58. i_type_2 = request.form.get("sel_qualification_type_2", 0, type=int)
  59. q_qualifications = db.session.query(tbl_qualifications.id, tbl_qualifications.i_qualification_type, tbl_qualifications.v_qualification_name).order_by(tbl_qualifications.v_qualification_name)
  60. l_qualifications_1 = q_qualifications.filter(tbl_qualifications.i_qualification_type==i_type_1).order_by(tbl_qualifications.v_qualification_name).all() if i_type_1>0 else []
  61. l_qualifications_2 = q_qualifications.filter(tbl_qualifications.i_qualification_type==i_type_2).order_by(tbl_qualifications.v_qualification_name).all() if i_type_2>0 else []
  62. l_qualifications_1 = list(map(dict_row, l_qualifications_1))
  63. l_qualifications_2 = list(map(dict_row, l_qualifications_2))
  64. for ix, d in enumerate(l_qualifications_1): l_qualifications_1[ix] = (d["id"], d["v_qualification_name"])
  65. for ix, d in enumerate(l_qualifications_2): l_qualifications_2[ix] = (d["id"], d["v_qualification_name"])
  66. filter_form.sel_qualification_1.choices = [(0, "---")] + l_qualifications_1
  67. filter_form.sel_qualification_2.choices = [(0, "---")] + l_qualifications_2
  68. # initiate query that will possibly get filtered
  69. q_records = db.session.query(tbl_records.id, tbl_records.v_name_1, tbl_records.v_surname, tbl_records.si_years_experience, tbl_records.v_contact_number, tbl_records.v_email)
  70. if filter_form.validate_on_submit():
  71. # possible filter values
  72. bl_case_sensitive, i_role_department_id, i_role_id, s_name, s_surname, s_id_number = (filter_form.chk_case_sensitive.data, filter_form.sel_role_department.data, filter_form.sel_role.data, str(filter_form.txt_name.data), str(filter_form.txt_surname.data), str(filter_form.txt_id_number.data))
  73. s_sap_k_level, s_language = (filter_form.sel_sap_k_level.data, filter_form.sel_language.data)
  74. i_qualification_type_1, i_qualification_type_2, i_qualification_1, i_qualification_2 = (filter_form.sel_qualification_type_1.data, filter_form.sel_qualification_type_2.data, filter_form.sel_qualification_1.data, filter_form.sel_qualification_2.data)
  75. # apply filters
  76. if i_role_department_id>0: q_records = q_records.filter(tbl_records.i_department_id==i_role_department_id)
  77. if i_role_id>0: q_records = q_records.filter(tbl_records.i_role_id==i_role_id)
  78. if s_name!="":
  79. s_name = f"%{s_name}%"
  80. if bl_case_sensitive:
  81. sq_names = select(tbl_records.id).filter(or_(tbl_records.v_name_1.like(s_name), tbl_records.v_name_2.like(s_name), tbl_records.v_name_3.like(s_name)))
  82. else:
  83. sq_names = select(tbl_records.id).filter(or_(func.lower(tbl_records.v_name_1).like(func.lower(s_name)), func.lower(tbl_records.v_name_2).like(func.lower(s_name)), func.lower(tbl_records.v_name_3).like(func.lower(s_name))))
  84. # end of checking if case sensitivity applies
  85. q_records = q_records.filter(tbl_records.id.in_(sq_names))
  86. # end of checking if need to check for name substrings
  87. if s_surname!="":
  88. s_surname = f"%{s_surname}%"
  89. if bl_case_sensitive:
  90. q_records = q_records.filter(tbl_records.v_surname.like(s_surname))
  91. else:
  92. q_records = q_records.filter(func.lower(tbl_records.v_surname).like(func.lower(s_surname)))
  93. # end of checking if case sensitivity applies
  94. # end of checking for surname filter
  95. if s_id_number!="":
  96. s_id_number = f"%{s_id_number}%"
  97. if bl_case_sensitive:
  98. q_records = q_records.filter(tbl_records.v_id_number.like(s_id_number))
  99. else:
  100. q_records = q_records.filter(func.lower(tbl_records.v_id_number).like(func.lower(s_id_number)))
  101. # end of checking if case sensitivity applies
  102. # end of checking for ID number filter
  103. if s_sap_k_level!="": q_records = q_records.filter(tbl_records.v_sap_k_level==s_sap_k_level)
  104. if s_language!="":
  105. sq_languages = select(tbl_languages.i_record_id.distinct()).filter(tbl_languages.v_language_abbreviation==s_language)
  106. q_records = q_records.filter(tbl_records.id.in_(sq))
  107. # end of checking if need to use language-matching subquery
  108. if i_qualification_type_1>0:
  109. if i_qualification_1>0:
  110. sq_qualification_1 = select(tbl_record_qualifications.i_record_id.distinct()).filter(tbl_record_qualifications.i_qualification_id==i_qualification_1)
  111. q_records = q_records.filter(tbl_records.id.in_(sq_qualification_1))
  112. else:
  113. sq_qualification_1 = select(tbl_record_qualifications.i_record_id.distinct()).join(tbl_qualifications, tbl_qualifications.id==tbl_record_qualifications.i_qualification_id, isouter=False).filter(tbl_qualifications.i_qualification_type==i_qualification_type_1)
  114. q_records = q_records.filter(tbl_records.id.in_(sq_qualification_1))
  115. # end of checking if need to filter qualification itself or just type
  116. # end of checking for qualification type 1
  117. if i_qualification_type_2>0:
  118. if i_qualification_2>0:
  119. sq_qualification_2 = select(tbl_record_qualifications.i_record_id.distinct()).filter(tbl_record_qualifications.i_qualification_id==i_qualification_2)
  120. q_records = q_records.filter(tbl_records.id.in_(sq_qualification_2))
  121. else:
  122. sq_qualification_2 = select(tbl_record_qualifications.i_record_id.distinct()).join(tbl_qualifications, tbl_qualifications.id==tbl_record_qualifications.i_qualification_id, isouter=False).filter(tbl_qualifications.i_qualification_type==i_qualification_type_2)
  123. q_records = q_records.filter(tbl_records.id.in_(sq_qualification_2))
  124. # end of checking if need to filter qualification itself or just type
  125. # end of checking for qualification type 2
  126. # end of checking for filter form submission
  127. i_page = request.form.get("hid_page", 1, type=int)
  128. q_records = q_records.order_by(tbl_records.v_surname, tbl_records.v_name_1)
  129. pagination = q_records.paginate(page=i_page, per_page=20)
  130. l_records = list(map(dict_row, pagination.items))
  131. # l_records = list(map(dict_row, q_records.all()))
  132. for ix, record in enumerate(l_records):
  133. i_record = record["id"]
  134. l_record_qualifications = list(map(dict_row, db.session.query(tbl_record_qualifications.id, tbl_qualification_types.v_qualification_type, tbl_qualifications.v_qualification_name, tbl_record_qualifications.d_acquired).join(tbl_qualifications, tbl_record_qualifications.i_qualification_id==tbl_qualifications.id, isouter=False).join(tbl_qualification_types, tbl_qualifications.i_qualification_type==tbl_qualification_types.id, isouter=False).filter(tbl_record_qualifications.i_record_id==i_record).order_by(tbl_record_qualifications.d_acquired, tbl_qualifications.v_qualification_name).all()))
  135. l_records[ix]["record_qualifications"] = l_record_qualifications
  136. # end of looping through records to retrieve additional information
  137. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  138. s_url = url_for("main.index")#, _external=True)
  139. return render_template("index.html", js=True, base=s_base, url=s_url, records=l_records, filter_form=filter_form, paging=pagination)
  140. except Exception as exc:
  141. return render_template_string(end_user_debugging("index/landing page"))
  142. # end of surrounding try-except
  143. # end of index view function for route /
  144. @bp.route("/icons", methods=["GET"])
  145. def icons():
  146. """icons preview - purely testing"""
  147. return render_template("icons.html")
  148. # end of icons view function for route /
  149. @bp.route("/capture_record/<int:i_record>/", methods=["GET", "POST"])
  150. @bp.route("/capture_record/", defaults={"i_record": 0}, methods=["GET", "POST"])
  151. @login_required
  152. @check_capture
  153. def capture_record(i_record:int = 0):
  154. """add/update C.V. record"""
  155. try:
  156. l_all_languages = list(map(dict_row, db.session.query(tbl_all_languages.v_language_abbreviation, tbl_all_languages.v_language_name).filter(tbl_all_languages.bl_south_african==True).order_by(tbl_all_languages.v_language_name).all()))
  157. l_departments = list(map(dict_row, db.session.query(tbl_role_departments.id, tbl_role_departments.v_department_name).order_by(tbl_role_departments.v_department_name).all()))
  158. l_roles = []
  159. i_department_id, i_role_id =(0, 0)
  160. form = RecordForm()
  161. remove_qualification_form = RecordQualificationRemovalForm()
  162. qualification_form = RecordQualificationForm()
  163. i_qualification_type_id = request.form.get("sel_qualification_type", default=0, type=int)
  164. l_qualification_types = list(map(dict_row, db.session.query(tbl_qualification_types.id, tbl_qualification_types.v_qualification_type).order_by(tbl_qualification_types.v_qualification_type).all()))
  165. l_qualifications = []
  166. if len(l_qualification_types)>0:
  167. i_qualification_type_id = int(l_qualification_types[0]["id"]) if i_qualification_type_id<1 else i_qualification_type_id
  168. l_qualifications = list(map(dict_row, db.session.query(tbl_qualifications.id, tbl_qualifications.v_qualification_name).filter(tbl_qualifications.i_qualification_type==i_qualification_type_id).order_by(tbl_qualifications.v_qualification_name).all()))
  169. if len(l_qualifications)>0:
  170. l_choices = list(map((lambda x : (x["id"], x["v_qualification_name"])), l_qualifications))
  171. qualification_form.sel_qualification.choices = l_choices
  172. # end of checking if qualifications were retrieved
  173. # end of checking if there are qualification types
  174. tender_form = TenderAllocationForm()
  175. tender_removal_form = TenderCVRemovalForm()
  176. if form.validate_on_submit() and request.form.get("btn_save", "")=="Save":
  177. s_action = "updated"
  178. o_record = None
  179. s_name_1, s_name_2, s_name_3, s_surname = (form.txt_name_1.data, form.txt_name_2.data, form.txt_name_3.data, form.txt_surname.data)
  180. s_id_number = form.txt_id_number.data
  181. s_gender = form.sel_gender.data
  182. i_years_experience = form.txt_years_experience.data
  183. s_sap_k_level = form.sel_sap_k_level.data
  184. s_contact_number = form.txt_contact_number.data
  185. s_email = form.txt_email.data
  186. i_department_id = request.form.get("sel_department_id", 0, type=int)
  187. i_role_id = request.form.get("sel_role_id", 0, type=int)
  188. l_languages = request.form.getlist("hid_langs")
  189. l_language_levels = request.form.getlist("hid_lang_levels")
  190. if i_record > 0:
  191. o_record = db.session.get(tbl_records, i_record)
  192. o_record.v_name_1, o_record.v_name_2, o_record.v_name_3, o_record.v_surname = (s_name_1, s_name_2, s_name_3, s_surname)
  193. o_record.v_id_number, o_record.c_gender, o_record.si_years_experience, o_record.v_sap_k_level = (s_id_number, s_gender, i_years_experience, s_sap_k_level)
  194. o_record.v_contact_number, o_record.v_email = (s_contact_number, s_email)
  195. o_record.i_department_id, o_record.i_role_id = (i_department_id, i_role_id)
  196. db.session.add(o_record)
  197. db.session.commit()
  198. else:
  199. s_action = "inserted"
  200. # save initial record
  201. o_record = tbl_records(v_name_1=s_name_1, v_name_2=s_name_2, v_name_3=s_name_3, v_surname=s_surname, v_id_number=s_id_number, c_gender=s_gender, si_years_experience=i_years_experience, v_sap_k_level=s_sap_k_level, v_contact_number=s_contact_number, v_email=s_email, i_department_id=i_department_id, i_role_id=i_role_id)
  202. db.session.add(o_record)
  203. db.session.commit()
  204. i_record = int(o_record.id) if o_record.id is not None else 0
  205. # end of checking if new or existing record
  206. # save languages linked to parent record
  207. if i_record > 0:
  208. if len(l_languages)==len(l_language_levels):
  209. # first remove any prior language records that do not match currently submitted languages
  210. q_delete = delete(tbl_languages).where(tbl_languages.i_record_id==i_record).where(tbl_languages.v_language_abbreviation.not_in(l_languages))
  211. db.session.execute(q_delete)
  212. # insert/update language records
  213. for ix, s_lang in enumerate(l_languages):
  214. s_language_name = ""
  215. q_language = db.session.query(tbl_all_languages.v_language_name).filter(tbl_all_languages.v_language_abbreviation==s_lang)
  216. if q_language.count() > 0:
  217. s_language_name = q_language.first()[0]
  218. # end of checking for matching language name record
  219. del q_language
  220. q_language = None
  221. if db.session.query(tbl_languages).filter(tbl_languages.i_record_id==i_record, tbl_languages.v_language_abbreviation==s_lang).count()<1:
  222. q_language = insert(tbl_languages).values({"i_record_id": i_record, "v_language_abbreviation": s_lang, "v_language_name": s_language_name, "si_level": int(l_language_levels[ix]), "si_ranking": ix+1})
  223. else:
  224. q_language = update(tbl_languages).where(tbl_languages.i_record_id==i_record,tbl_languages.v_language_abbreviation==s_lang).values({"v_language_name": s_language_name, "si_level": int(l_language_levels[ix]), "si_ranking": ix+1})
  225. # end of checking if insert or update
  226. db.session.execute(q_language)
  227. db.session.commit()
  228. # end of looping through languages
  229. # end of checking language list lengths
  230. flash(f"Record successfully {s_action}", "success")
  231. # end of checking if initial record submission took place
  232. elif qualification_form.validate_on_submit() and i_record>0 and request.form.get("btn_save_qualification", "")=="Save":
  233. i_record_qualification_id, i_qualification_id, d_acquired = (int(qualification_form.hid_record_qualification_id.data), int(qualification_form.sel_qualification.data), qualification_form.d_acquired.data)
  234. if i_record_qualification_id>0:
  235. o_record_qualification = db.session.get(tbl_record_qualifications, i_record_qualification_id)
  236. if o_record_qualification is not None:
  237. o_record_qualification.i_qualification_id = i_qualification_id
  238. o_record_qualification.d_acquired = d_acquired
  239. db.session.add(o_record_qualification)
  240. db.session.commit()
  241. flash("Qualification record updated")
  242. # end of checking if prior record retrieved
  243. else:
  244. o_record_qualification = tbl_record_qualifications(i_record_id=i_record, i_qualification_id=i_qualification_id, d_acquired=d_acquired)
  245. db.session.add(o_record_qualification)
  246. db.session.commit()
  247. flash("Qualification record recorded")
  248. # end of checking if existing or new qualification record
  249. elif remove_qualification_form.validate_on_submit() and request.form.get("hid_remove_qualification_id", None) is not None:
  250. i_qualification_removal_id = request.form.get("hid_remove_qualification_id", type=int)
  251. o_record_qualification = db.session.get(tbl_record_qualifications, i_qualification_removal_id)
  252. if o_record_qualification is not None:
  253. db.session.delete(o_record_qualification)
  254. db.session.commit()
  255. flash("Qualification removed")
  256. # end of making sure retrieved record
  257. elif tender_removal_form.validate_on_submit() and request.form.get("hid_tender_cv_id", None) is not None:
  258. i_tender_cv_id = int(tender_removal_form.hid_tender_cv_id.data)
  259. db.session.execute(delete(tbl_tender_cvs).where(tbl_tender_cvs.id==i_tender_cv_id))
  260. db.session.commit()
  261. flash("Tender allocation removed", "success")
  262. # end of checking for form submission initially
  263. # populate possible tender allocation choices
  264. sq_tenders = select(tbl_tender_cvs.i_tender_id.distinct()).filter(tbl_tender_cvs.i_record_id==i_record)
  265. l_other_tenders = list(map(dict_row, db.session.query(tbl_tenders.id, tbl_tenders.v_reference_number, tbl_tenders.v_description).filter(tbl_tenders.id.not_in(sq_tenders)).all()))
  266. if len(l_other_tenders)>0:
  267. l_tender_choices = []
  268. for ix, d_tender in enumerate(l_other_tenders):
  269. l_tender_choices.append((d_tender["id"], " - ".join((d_tender["v_reference_number"], str(d_tender["v_description"])[:20]))))
  270. # end of looping through other tenders
  271. tender_form.sel_tender.choices = tender_form.sel_tender.choices + l_tender_choices
  272. # end of checking if need to populate tender choices for possible allocation
  273. # second form submission check after cv allocation might have been updated - because am populating tender choices here b4 validation
  274. if tender_form.validate_on_submit() and request.form.get("btn_allocate", "")=="Allocate":
  275. i_tender_id = int(tender_form.sel_tender.data)
  276. if i_tender_id>0:
  277. db.session.add(tbl_tender_cvs(i_tender_id=i_tender_id, i_record_id=i_record))
  278. db.session.commit()
  279. flash("Tender allocated", "success")
  280. # update tender allocation choices
  281. tender_form.sel_tender.choices = list(filter((lambda x : x[0]!=i_tender_id), tender_form.sel_tender.choices))
  282. # end of making sure actual tender record was selected
  283. # end of second form submission check
  284. form.hid_record_id.data = i_record
  285. remove_qualification_form.hid_remove_qualification_id.data = 0
  286. tender_form.sel_tender.data = 0
  287. # populate record info if existing record
  288. if i_record>0:
  289. o_record = db.session.get(tbl_records, i_record)
  290. if o_record is not None:
  291. form.hid_record_id.data = i_record
  292. if o_record.i_department_id>0:
  293. i_department_id = o_record.i_department_id
  294. i_role_id = o_record.i_role_id
  295. l_roles = list(map(dict_row, db.session.query(tbl_roles.id, tbl_roles.v_role_name).filter(tbl_roles.i_department_id==i_department_id).order_by(tbl_roles.v_role_name).all()))
  296. # end of checking which department roles should populate
  297. form.txt_name_1.data, form.txt_name_2.data, form.txt_name_3.data, form.txt_surname.data = (o_record.v_name_1, o_record.v_name_2, o_record.v_name_3, o_record.v_surname)
  298. form.txt_id_number.data = o_record.v_id_number
  299. form.sel_gender.data = o_record.c_gender
  300. form.txt_years_experience.data = o_record.si_years_experience
  301. form.sel_sap_k_level.data = o_record.v_sap_k_level
  302. form.txt_contact_number.data = o_record.v_contact_number
  303. form.txt_email.data = o_record.v_email
  304. else:
  305. i_record = 0
  306. # end of making sure valid record
  307. # end of checking if existing record
  308. qualification_form.hid_record_qualification_id.data, qualification_form.sel_qualification.data, qualification_form.d_acquired.data = (0, None, None)
  309. # form.txt_name_1.data, form.txt_name_2.data, form.txt_name_3.data, form.txt_surname.data = ("", "", "", "")
  310. # form.txt_id_number.data, form.sel_gender.data, form.txt_years_experience.data, form.sel_sap_k_level.data, form.txt_contact_number.data, form.txt_email.data = ("", "m", "0", "", "", "")
  311. l_languages = db.session.query(tbl_languages.v_language_abbreviation, tbl_languages.v_language_name, tbl_languages.si_level).filter(tbl_languages.i_record_id==i_record).order_by(tbl_languages.si_ranking).all()
  312. l_languages = list(map(dict_row, l_languages))
  313. l_record_qualifications = list(map(dict_row, db.session.query(tbl_record_qualifications.id, tbl_qualification_types.v_qualification_type, tbl_qualifications.v_qualification_name, tbl_record_qualifications.d_acquired).join(tbl_qualifications, tbl_record_qualifications.i_qualification_id==tbl_qualifications.id, isouter=False).join(tbl_qualification_types, tbl_qualifications.i_qualification_type==tbl_qualification_types.id, isouter=False).filter(tbl_record_qualifications.i_record_id==i_record).order_by(tbl_record_qualifications.d_acquired, tbl_qualifications.v_qualification_name).all()))
  314. l_prior_tenders = list(map(dict_row, db.session.query(tbl_tender_cvs.id, tbl_tenders.v_reference_number, tbl_tenders.v_description, tbl_tender_cvs.dt_when).join(tbl_tenders, tbl_tenders.id==tbl_tender_cvs.i_tender_id, isouter=False).filter(tbl_tender_cvs.i_record_id==i_record).order_by(tbl_tender_cvs.dt_when.desc()).limit(3)))
  315. for ix, t in enumerate(l_prior_tenders):
  316. l_prior_tenders[ix]["dt_when"] = l_prior_tenders[ix]["dt_when"].strftime("%Y-%m-%d %H:%M")
  317. # end of updating values in prior tenders
  318. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  319. s_url = url_for("main.capture_record")#, _external=True)
  320. return render_template("capture_record.html", js=True, base=s_base, url=s_url, form=form, record_id=i_record, languages=l_languages, all_languages=l_all_languages, qualification_types=l_qualification_types, qualification_form=qualification_form, record_qualifications=l_record_qualifications, departments=l_departments, roles=l_roles, department_id=i_department_id, role_id=i_role_id, remove_qualification_form=remove_qualification_form, prior_tenders=l_prior_tenders, tender_form=tender_form, remove_tender_form=tender_removal_form)
  321. except Exception as exc:
  322. return render_template_string(end_user_debugging("capture_record"))
  323. # end of surrounding try-except
  324. # end of capture_record view function for route /capture_record
  325. @bp.route("/download_upload/<int:i_upload>/<bl_download>/", methods=["GET"])
  326. @bp.route("/download_upload/<int:i_upload>/", defaults={"bl_download": True}, methods=["GET"])
  327. @login_required
  328. def download_upload(i_upload:int = 0, bl_download:bool = True):
  329. """open upload within browser or activate download thereof"""
  330. try:
  331. bl_download = True if str(bl_download)=="True" else False
  332. bl_download = bool(bl_download)
  333. o_out = None
  334. o_upload = db.session.get(tbl_uploads, i_upload)
  335. if o_upload is not None:
  336. o_out = io.BytesIO(o_upload.b_file)
  337. s_mimetype = o_upload.v_mime_type
  338. s_filename = o_upload.v_filename
  339. s_mimetype = "application/msword" if s_filename.lower().endswith(".doc") else "application/vnd.openxmlformats-officedocument.wordprocessingml.document" if s_filename.lower().endswith(".docx") else s_mimetype
  340. resp = send_file(o_out, mimetype=s_mimetype, as_attachment=bl_download, download_name=s_filename)
  341. # resp = make_response(o_out)
  342. # resp.headers.set("Content-Disposition", f"attachment; filename=\"{s_filename}\"" if bl_download else f"inline; filename=\"{s_filename}\"")
  343. # resp.headers.set("Content-Type", s_mimetype)
  344. return resp
  345. else:
  346. return make_response("No upload retrieved", 500)
  347. # end of None check for record
  348. except Exception as exc:
  349. return render_template_string(end_user_debugging("download_upload"))
  350. # end of surrounding try-except
  351. # end of download_upload view function for route /download_upload/<int:i_upload>/[<bl_download>]
  352. def qualification_type_choices():
  353. """return possible qualification type choices"""
  354. l_choices = []
  355. l_qualification_types = list(map(dict_row, db.session.query(tbl_qualification_types.id, tbl_qualification_types.v_qualification_type).order_by(tbl_qualification_types.v_qualification_type).all()))
  356. l_type_choices = list(map((lambda x : (x["id"], x["v_qualification_type"])), l_qualification_types))
  357. if len(l_type_choices)>0:
  358. l_choices = l_type_choices
  359. # end of checking if type choices exist
  360. return (l_qualification_types, l_choices)
  361. # end of qualification_type_choices function
  362. @bp.route("/qualifications/", methods=["GET", "POST"])
  363. @login_required
  364. @check_admin
  365. def qualifications():
  366. """manage qualification types and qualifications to make use of in rest of site"""
  367. try:
  368. i_type_filter = 0
  369. type_form = QualificationTypeForm()
  370. form = QualificationForm()
  371. l_qualification_types, form.sel_qualification_type.choices = qualification_type_choices()
  372. remove_type_form = QualificationTypeRemovalForm()
  373. remove_form = QualificationRemovalForm()
  374. if type_form.validate_on_submit() and request.form.get("btn_save_type", "")=="Save":
  375. i_type_id, s_type = (int(type_form.hid_qualification_type_id.data), type_form.txt_qualification_type.data)
  376. if i_type_id > 0:
  377. o_type = db.session.get(tbl_qualification_types, i_type_id)
  378. if o_type is not None:
  379. o_type.v_qualification_type = s_type
  380. db.session.add(o_type)
  381. db.session.commit()
  382. flash("Type updated", "success")
  383. # end of checking if record existed
  384. else:
  385. if db.session.query(tbl_qualification_types.id).filter(tbl_qualification_types.v_qualification_type==s_type).count()<1:
  386. db.session.add(tbl_qualification_types(v_qualification_type=s_type))
  387. db.session.commit()
  388. flash("Type inserted", "success")
  389. # end of making sure new type is not a duplicate
  390. # end of checking if new or existing type record
  391. elif remove_type_form.validate_on_submit() and request.form.get("hid_remove_qualification_type_id", None) is not None:
  392. i_type_id = int(remove_type_form.hid_remove_qualification_type_id.data)
  393. o_type = db.session.get(tbl_qualification_types, i_type_id)
  394. if o_type is not None:
  395. db.session.delete(o_type)
  396. db.session.commit()
  397. flash("Type removed", "success")
  398. # end of checking if record existed
  399. elif form.validate_on_submit() and request.form.get("btn_save_qualification", "")=="Save":
  400. i_qualification_id, i_qualification_type, s_qualification_name, s_description = (int(form.hid_qualification_id.data), int(form.sel_qualification_type.data), str(form.txt_qualification_name.data), str(form.txt_description.data))
  401. if i_qualification_id>0:
  402. o_qualification = db.session.get(tbl_qualifications, i_qualification_id)
  403. if o_qualification is not None:
  404. if db.session.query(tbl_qualifications).filter(tbl_qualifications.id!=i_qualification_id, tbl_qualifications.v_qualification_name==s_qualification_name, tbl_qualifications.i_qualification_type==i_qualification_type).count()<1:
  405. o_qualification.i_qualification_type = i_qualification_type
  406. o_qualification.v_qualification_name = s_qualification_name
  407. o_qualification.v_description = s_description
  408. db.session.add(o_qualification)
  409. db.session.commit()
  410. flash("Qualification updated")
  411. i_type_filter = i_qualification_type
  412. else:
  413. flash("There is already a qualification under same type category with same name")
  414. i_type_filter = i_qualification_type
  415. # end of checking for alternative duplicate
  416. # end of retrieval check
  417. else:
  418. if db.session.query(tbl_qualifications).filter(tbl_qualifications.v_qualification_name==s_qualification_name, tbl_qualifications.i_qualification_type==i_qualification_type).count()<1:
  419. o_qualification = tbl_qualifications(i_qualification_type=i_qualification_type, v_qualification_name=s_qualification_name, v_description=s_description)
  420. db.session.add(o_qualification)
  421. db.session.commit()
  422. flash("Qualification added")
  423. i_type_filter = i_qualification_type
  424. else:
  425. flash("There is an existing qualification with same name under same type category")
  426. # end of making sure would not count as duplicate
  427. # end of checking if new or existing qualification record
  428. elif remove_form.validate_on_submit() and request.form.get("hid_remove_qualification_id", None) is not None:
  429. i_qualification_id = int(remove_form.hid_remove_qualification_id.data)
  430. o_qualification = db.session.get(tbl_qualifications, i_qualification_id)
  431. if o_qualification is not None:
  432. db.session.delete(o_qualification)
  433. db.session.commit()
  434. flash("Qualification removed", "success")
  435. # end of checking if record existed
  436. # end of checking for form submissions
  437. type_form.hid_qualification_type_id.data, type_form.txt_qualification_type.data = (0, "")
  438. remove_type_form.hid_remove_qualification_type_id.data = 0
  439. # re-populate in case changes have occurred
  440. l_qualification_types, form.sel_qualification_type.choices = qualification_type_choices()
  441. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  442. s_url = url_for("main.qualifications")#, _external=True)
  443. return render_template("qualifications.html", js=True, base=s_base, url=s_url, type_form=type_form, form=form, qualification_types=l_qualification_types, remove_type_form=remove_type_form, type_filter=i_type_filter, remove_form=remove_form)
  444. except Exception as exc:
  445. return render_template_string(end_user_debugging("qualifications"))
  446. # end of surrounding try-except
  447. # end of qualifications view function for route /qualifications
  448. @bp.route("/qualifications_list/<int:i_type_id>/", methods=["GET"])
  449. @login_required
  450. def qualifications_list(i_type_id):
  451. """return list of qualification entries that match type filter"""
  452. try:
  453. l_out = []
  454. q_qualifications = db.session.query(tbl_qualifications.id, tbl_qualifications.v_qualification_name, tbl_qualifications.v_description, tbl_qualification_types.v_qualification_type).join(tbl_qualification_types, tbl_qualifications.i_qualification_type==tbl_qualification_types.id, isouter=True)
  455. q_qualifications = q_qualifications.filter(tbl_qualifications.i_qualification_type==i_type_id) if i_type_id>0 else q_qualifications
  456. if q_qualifications.count()>0:
  457. q_qualifications = q_qualifications.order_by(tbl_qualifications.v_qualification_name)
  458. l_out = list(map(dict_row, q_qualifications.all())) if q_qualifications.count()>0 else []
  459. # end of checking result count of query
  460. return make_response(jsonify(l_out), 200)
  461. except Exception as exc:
  462. return render_template_string(end_user_debugging("qualifications_list"))
  463. # end of surrounding try-except
  464. # end of qualifications_list view function
  465. @bp.route("/qualification_details/<int:i_qualification_id>/", methods=["GET"])
  466. @login_required
  467. def qualification_details(i_qualification_id):
  468. """retrieve qualification details"""
  469. try:
  470. d_out = {}
  471. o_qualification = db.session.get(tbl_qualifications, i_qualification_id)
  472. if o_qualification is not None:
  473. d_out = dict_instance(o_qualification)
  474. # end of checking if record retrieved
  475. return make_response(jsonify(d_out), 200)
  476. except Exception as exc:
  477. return render_template_string(end_user_debugging("qualification_details"))
  478. # end of surrounding try-except
  479. # end of qualification_details view function
  480. def role_department_choices():
  481. """return list of possible|existing role departments"""
  482. l_choices = []
  483. l_departments = list(map(dict_row, db.session.query(tbl_role_departments.id, tbl_role_departments.v_department_name).order_by(tbl_role_departments.v_department_name).all()))
  484. l_department_choices = list(map((lambda x : (x["id"], x["v_department_name"])), l_departments))
  485. if len(l_department_choices)>0:
  486. l_choices = l_department_choices
  487. # end of checking if there were records
  488. return (l_departments, l_choices)
  489. # end of role_department_choices function
  490. @bp.route("/roles/", methods=["GET", "POST"])
  491. @login_required
  492. @check_admin
  493. def roles():
  494. """manage roles available for assignment to records"""
  495. try:
  496. i_department_filter = 0
  497. department_form = RoleDepartmentForm()
  498. form = RoleForm()
  499. # need to populate this first to allow form validation
  500. l_departments, form.sel_department.choices = role_department_choices()
  501. remove_department_form = RoleDepartmentRemovalForm()
  502. remove_form = RoleRemovalForm()
  503. if department_form.validate_on_submit() and request.form.get("btn_save_department", "")=="Save":
  504. i_department_id, s_department_name = (int(department_form.hid_department_id.data), department_form.txt_department_name.data)
  505. if i_department_id > 0:
  506. o_department = db.session.get(tbl_role_departments, i_department_id)
  507. if o_department is not None:
  508. o_department.v_department_name = s_department_name
  509. db.session.add(o_department)
  510. db.session.commit()
  511. flash("Department updated", "success")
  512. # end of checking if record existed
  513. else:
  514. if db.session.query(tbl_role_departments.id).filter(tbl_role_departments.v_department_name==s_department_name).count()<1:
  515. db.session.add(tbl_role_departments(v_department_name=s_department_name))
  516. db.session.commit()
  517. flash("Department inserted", "success")
  518. # end of making sure new department is not a duplicate
  519. # end of checking if new or existing department record
  520. elif form.validate_on_submit() and request.form.get("btn_save_role", "")=="Save":
  521. i_role_id, i_department_id, s_role_name, s_description = (int(form.hid_role_id.data), int(form.sel_department.data), str(form.txt_role_name.data), str(form.txt_description.data))
  522. if i_role_id>0:
  523. o_role = db.session.get(tbl_roles, i_role_id)
  524. if o_role is not None:
  525. if db.session.query(tbl_roles).filter(tbl_roles.id!=i_role_id, tbl_roles.v_role_name==s_role_name, tbl_roles.i_department_id==i_department_id).count()<1:
  526. o_role.i_department_id = i_department_id
  527. o_role.v_role_name = s_role_name
  528. o_role.v_description = s_description
  529. db.session.add(o_role)
  530. db.session.commit()
  531. flash("Role updated")
  532. i_department_filter = i_department_id
  533. else:
  534. flash("There is already a role under same department with same name")
  535. i_department_filter = i_department_id
  536. # end of checking for alternative duplicate
  537. # end of retrieval check
  538. else:
  539. if db.session.query(tbl_roles).filter(tbl_roles.v_role_name==s_role_name, tbl_roles.i_department_id==i_department_id).count()<1:
  540. o_role = tbl_roles(i_department_id=i_department_id, v_role_name=s_role_name, v_description=s_description)
  541. db.session.add(o_role)
  542. db.session.commit()
  543. flash("Role added")
  544. i_department_filter = i_department_id
  545. else:
  546. flash("There is an existing role with same name under same department")
  547. # end of making sure would not count as duplicate
  548. # end of checking if new or existing role record
  549. elif remove_department_form.validate_on_submit():
  550. i_department_id = int(remove_department_form.hid_remove_department_id.data)
  551. o_department = db.session.get(tbl_role_departments, i_department_id)
  552. if o_department is not None:
  553. db.session.delete(o_department)
  554. db.session.commit()
  555. flash("Department removed", "success")
  556. # end of checking if record existed
  557. elif remove_form.validate_on_submit():
  558. i_role_id = int(remove_form.hid_remove_role_id.data)
  559. o_role = db.session.get(tbl_roles, i_role_id)
  560. if o_role is not None:
  561. db.session.delete(o_role)
  562. db.session.commit()
  563. flash("Role removed", "success")
  564. # end of checking if record existed
  565. # end of checking for form submission
  566. department_form.hid_department_id.data, department_form.txt_department_name.data = (0, "")
  567. remove_form.hid_remove_role_id.data, remove_department_form.hid_remove_department_id.data = ("0", "0")
  568. # re-populate in case changes have occurred
  569. l_departments, form.sel_department.choices = role_department_choices()
  570. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  571. s_url = url_for("main.roles")#, _external=True)
  572. return render_template("roles.html", js=True, base=s_base, url=s_url, departments=l_departments, department_filter=i_department_filter, department_form=department_form, form=form, remove_department_form=remove_department_form, remove_form=remove_form)
  573. except Exception as exc:
  574. return render_template_string(end_user_debugging("roles"))
  575. # end of surrounding try-except
  576. # end of roles view function for route /roles
  577. @bp.route("/roles_list/<int:i_department_id>/", methods=["GET"])
  578. @login_required
  579. def roles_list(i_department_id):
  580. """return list of role entries that match department filter"""
  581. try:
  582. l_out = []
  583. try:
  584. q_roles = db.session.query(tbl_roles.id, tbl_roles.v_role_name, tbl_roles.v_description, tbl_role_departments.v_department_name).join(tbl_role_departments, tbl_roles.i_department_id==tbl_role_departments.id, isouter=True)
  585. q_roles = q_roles.filter(tbl_roles.i_department_id==i_department_id) if i_department_id>0 else q_roles
  586. if q_roles.count()>0:
  587. q_roles = q_roles.order_by(tbl_roles.v_role_name)
  588. l_out = list(map(dict_row, q_roles.all()))
  589. # end of checking result count of query
  590. except Exception as exc:
  591. print(extract_exc("list roles?"))
  592. # end of try-except
  593. return make_response(jsonify(l_out), 200)
  594. except Exception as exc:
  595. return render_template_string(end_user_debugging("roles_list"))
  596. # end of surrounding try-except
  597. # end of roles_list view function for route /roles_list/<int:i_department_id>/
  598. @bp.route("/role_details/<int:i_role_id>/", methods=["GET"])
  599. @login_required
  600. def role_details(i_role_id):
  601. """retrieve role details"""
  602. try:
  603. d_out = {}
  604. o_role = db.session.get(tbl_roles, i_role_id)
  605. if o_role is not None:
  606. d_out = dict_instance(o_role)
  607. # end of checking if record retrieved
  608. return make_response(jsonify(d_out), 200)
  609. except Exception as exc:
  610. return render_template_string(end_user_debugging("role_details"))
  611. # end of surrounding try-except
  612. # end of role_details view function for route /role_details/<int:i_role_id>/
  613. @bp.route("/uploads/<int:i_record>/", methods=["GET", "POST"])
  614. @login_required
  615. @check_capture
  616. def uploads(i_record:int = 0):
  617. """capture uploads linked to CV record"""
  618. try:
  619. # https://blog.miguelgrinberg.com/post/handling-file-uploads-with-flask
  620. o_record = db.session.get(tbl_records, i_record)
  621. if o_record is None:
  622. return redirect(url_for("main.index"))
  623. # end of just making sure valid record
  624. remove_upload_form = UploadRemovalForm()
  625. upload_form = UploadForm()
  626. s_names = o_record.v_surname + ", " + " ".join([o_record.v_name_1, o_record.v_name_2, o_record.v_name_3])
  627. l_qualifications = list(map(dict_row, db.session.query(tbl_record_qualifications.id, tbl_qualifications.v_qualification_name, tbl_record_qualifications.d_acquired).join(tbl_qualifications, tbl_record_qualifications.i_qualification_id==tbl_qualifications.id, isouter=False).filter(tbl_record_qualifications.i_record_id==i_record).order_by(tbl_qualifications.v_qualification_name).all()))
  628. upload_form.sel_match.choices.extend(list(map((lambda x : (x["id"], x["v_qualification_name"])), l_qualifications)))
  629. if upload_form.validate_on_submit() and request.form.get("btn_upload", None) is not None:
  630. i_upload_id, i_upload_type, i_matching_id, s_description = (int(upload_form.hid_upload_id.data), int(upload_form.sel_upload_type.data), int(upload_form.sel_match.data), str(upload_form.txt_description.data))
  631. fil_upload = request.files.get("fil_upload_document")
  632. if fil_upload is not None and fil_upload.filename!="":
  633. s_ext = os.path.splitext(fil_upload.filename)[1]
  634. if s_ext not in [".jpg", ".png", ".pdf", ".doc", ".docx"]:
  635. abort(400)
  636. # end of double-checking file extension
  637. # extract data record values
  638. s_filename= fil_upload.filename
  639. s_mime_type = fil_upload.mimetype
  640. try:
  641. if i_upload_id>0:
  642. q_update = update(tbl_uploads).where(id==i_upload_id)
  643. q_update = q_update.values({"i_record_id": i_record, "i_matching_id": i_matching_id, "si_upload_type": i_upload_type, "v_description": s_description, "v_filename": s_filename, "v_mime_type": s_mime_type, "b_file": fil_upload.read()})
  644. db.session.execute(q_update)
  645. flash("Document record updated")
  646. else:
  647. o_upload = tbl_uploads(i_record_id=i_record, i_matching_id=i_matching_id, si_upload_type=i_upload_type, v_description=s_description, v_filename=s_filename, v_mime_type=s_mime_type, b_file=fil_upload.read())
  648. db.session.add(o_upload)
  649. db.session.commit()
  650. flash("Document uploaded")
  651. except Exception as exc:
  652. print(str(exc))
  653. # end of try-except around saving data record
  654. else:
  655. flash("Invalid file upload")
  656. # end of making sure file was uploaded
  657. elif remove_upload_form.validate_on_submit():
  658. i_upload_id = int(remove_upload_form.hid_remove_upload_id.data)
  659. o_upload = db.session.get(tbl_uploads, i_upload_id)
  660. if o_upload is not None:
  661. db.session.delete(o_upload)
  662. db.session.commit()
  663. flash("Uploaded document removed")
  664. # end of checking if record was retrieved
  665. # end of checking for form submission
  666. remove_upload_form.hid_remove_upload_id.data = 0
  667. upload_form.hid_upload_id.data, upload_form.sel_upload_type.data, upload_form.sel_match.data, upload_form.txt_description.data = (0, 0, 0, "")
  668. l_uploads = list(map(dict_row, db.session.query(tbl_uploads.id, tbl_uploads.si_upload_type, tbl_uploads.v_description, tbl_uploads.v_filename, tbl_qualifications.v_qualification_name).join(tbl_qualifications, tbl_qualifications.id==tbl_uploads.i_matching_id, isouter=True).filter(tbl_uploads.i_record_id==i_record).order_by(tbl_uploads.v_filename).all()))
  669. for ix, d in enumerate(l_uploads): d["si_upload_type"] = {0: "Original CV", 1: "Alteram CV", 2: "Certificate/Diploma/Degree", 3: "I.D. Document or Passport"}[d["si_upload_type"]]; d["v_qualification_name"] = d["v_qualification_name"] if d["v_qualification_name"] is not None else ""
  670. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  671. s_url = url_for("main.uploads", i_record=i_record)#, _external=True)
  672. return render_template("uploads.html", js=True, base=s_base, url=s_url, record_id=i_record, names=s_names, record_qualifications=l_qualifications, remove_upload_form=remove_upload_form, upload_form=upload_form, document_uploads=l_uploads)
  673. except Exception as exc:
  674. return render_template_string(end_user_debugging("uploads"))
  675. # end of surrounding try-except
  676. # end of uploads view function for route /uploads/<int:i_record>/
  677. @bp.route("/record_details/<int:i_record_id>/", methods=["GET"])
  678. @login_required
  679. def record_details(i_record_id):
  680. """view all details for a record"""
  681. try:
  682. d_out = {}
  683. q_details = db.session.query(tbl_records.id, tbl_records.v_surname, tbl_records.v_name_1, tbl_records.v_name_2, tbl_records.v_name_3, tbl_records.v_id_number, tbl_records.c_gender, tbl_records.si_years_experience, tbl_records.v_sap_k_level, tbl_records.v_contact_number, tbl_records.v_email, tbl_role_departments.v_department_name, tbl_roles.v_role_name).join(tbl_role_departments, tbl_role_departments.id==tbl_records.i_department_id, isouter=True).join(tbl_roles, tbl_roles.id==tbl_records.i_role_id, isouter=True).filter(tbl_records.id==i_record_id)
  684. if q_details.count()>0:
  685. d_out["record"] = dict_row(q_details.first())
  686. d_out["languages"] = list(map(dict_row, db.session.query(tbl_languages.v_language_abbreviation, tbl_languages.v_language_name, tbl_languages.si_level).filter(tbl_languages.i_record_id==i_record_id).order_by(tbl_languages.si_ranking).all()))
  687. for ix, d in enumerate(d_out["languages"]): d_out["languages"][ix]["level"] = ["basic", "intermediate", "proficient"][d["si_level"]]
  688. d_out["qualifications"] = list(map(dict_row, db.session.query(tbl_record_qualifications.d_acquired, tbl_qualifications.v_qualification_name, tbl_qualification_types.v_qualification_type).join(tbl_qualifications, tbl_qualifications.id==tbl_record_qualifications.i_qualification_id, isouter=True).join(tbl_qualification_types, tbl_qualification_types.id==tbl_qualifications.i_qualification_type, isouter=False).filter(tbl_record_qualifications.i_record_id==i_record_id).all()))
  689. for ix, d in enumerate(d_out["qualifications"]):
  690. if isinstance(d["d_acquired"], datetime_class.date): d["d_acquired"] = d["d_acquired"].strftime("%Y-%m-%d")
  691. # end of looping through qualifications
  692. d_out["uploads"] = list(map(dict_row, db.session.query(tbl_uploads.id, tbl_uploads.si_upload_type, tbl_uploads.v_filename, tbl_uploads.v_description, tbl_qualifications.v_qualification_name).join(tbl_qualifications, tbl_qualifications.id==tbl_uploads.i_matching_id, isouter=True).filter(tbl_uploads.i_record_id==i_record_id).all()))
  693. for ix, d in enumerate(d_out["uploads"]): d["si_upload_type"] = {0: "Original CV", 1: "Alteram CV", 2: "Certificate/Diploma/Degree", 3: "I.D. Document or Passport"}[d["si_upload_type"]]
  694. d_out["tenders"] = list(map(dict_row, db.session.query(tbl_tender_cvs.id, tbl_tenders.v_reference_number).join(tbl_tenders, tbl_tenders.id==tbl_tender_cvs.i_tender_id, isouter=False).filter(tbl_tender_cvs.i_record_id==i_record_id).order_by(tbl_tender_cvs.dt_when).limit(3)))
  695. # end of initial query length check
  696. return make_response(jsonify(d_out), 200)
  697. except Exception as exc:
  698. s_logging = end_user_debugging("record_details", False)
  699. return make_response(s_logging, 200)
  700. # end of surrounding try-except
  701. # end of record_details view function for route /record_details/<int:i_record_id>/
  702. @bp.route("/users/", methods=["GET", "POST"])
  703. @login_required
  704. @check_admin
  705. def users():
  706. """manage access|authentication IDs for admin, capture and view"""
  707. try:
  708. form = UserForm()
  709. removal_form = UserRemovalForm()
  710. if form.validate_on_submit() and request.form.get("btn_save", None)=="Save":
  711. i_user_id, s_user_id, s_password, s_password_confirm, bl_admin, bl_capture = (int(form.hid_user_id.data), str(form.txt_user_id.data), str(form.txt_password.data), str(form.txt_password_confirm.data), bool(form.chk_admin.data), bool(form.chk_capture.data))
  712. if i_user_id>0:
  713. o_user = db.session.get(tbl_users, i_user_id)
  714. if o_user is not None:
  715. if db.session.query(tbl_users.id).filter(tbl_users.id!=i_user_id, tbl_users.v_user_id==s_user_id).count()>0:
  716. flash("Another User with same User ID exists")
  717. else:
  718. o_user.v_user_id = s_user_id
  719. o_user.bl_admin = bl_admin
  720. o_user.bl_capture = bl_capture
  721. s_password_change = ""
  722. if len(s_password)>3 and s_password==s_password_confirm:
  723. o_user.set_password(s_password)
  724. s_password_change = " (with password changed)"
  725. # end of checking if password must be changed
  726. db.session.add(o_user)
  727. db.session.commit()
  728. flash(f"User record updated{s_password_change}")
  729. # end of checking for another user record with same user ID
  730. # end of checking for record retrieval
  731. else:
  732. if len(s_password)>3 and s_password==s_password_confirm:
  733. if db.session.query(tbl_users.id).filter(tbl_users.v_user_id==s_user_id).count()>0:
  734. flash("Another User with same User ID already exists")
  735. else:
  736. o_user = tbl_users(v_user_id=s_user_id, v_password=s_password, bl_admin=bl_admin, bl_capture=bl_capture)
  737. db.session.add(o_user)
  738. db.session.commit()
  739. i_user_id = int(o_user.id) if o_user.id is not None else 0
  740. if o_user.id>0:
  741. flash("User record inserted")
  742. # end of checking for record insertion
  743. # end of checking for matching record with same User ID
  744. else:
  745. flash("Password values must be longer than 3 characters, and must match")
  746. # end of making sure passwords match
  747. # end of checking if existing user
  748. elif removal_form.validate_on_submit():
  749. i_user_id = int(removal_form.hid_remove_user_id.data)
  750. o_user = db.session.get(tbl_users, i_user_id)
  751. if o_user is not None:
  752. db.session.delete(o_user)
  753. db.session.commit()
  754. flash("User record removed")
  755. # end of checking for record retrieval
  756. # end of checking for form submission
  757. form.hid_user_id.data, form.txt_user_id.data, form.txt_password.data, form.txt_password_confirm.data, form.chk_admin.data, form.chk_capture.data = (0, "", "", "", False, False)
  758. removal_form.hid_remove_user_id.data = 0
  759. l_users = list(map(dict_row, db.session.query(tbl_users.id, tbl_users.v_user_id, tbl_users.bl_admin, tbl_users.bl_capture).filter(tbl_users.v_user_id!="admin").order_by(tbl_users.v_user_id).all()))
  760. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  761. s_url = url_for("main.users")#, _external=True)
  762. return render_template("users.html", js=True, base=s_base, url=s_url, users=l_users, form=form, removal_form=removal_form)
  763. except Exception as exc:
  764. return render_template_string(end_user_debugging("users"))
  765. # end of surrounding try-except
  766. # end of users view function for route /users
  767. @bp.route("/user_details/<int:i_user_id>/", methods=["GET"])
  768. @login_required
  769. @check_admin
  770. def user_details(i_user_id:int = 0):
  771. """retrieve user record details"""
  772. try:
  773. d_out = {}
  774. o_user = db.session.get(tbl_users, i_user_id)
  775. if o_user is not None:
  776. d_out = dict_instance(o_user)
  777. del d_out["v_password"]
  778. # end of checking for record retrieval
  779. return make_response(jsonify(d_out), 200)
  780. except Exception as exc:
  781. return render_template_string(end_user_debugging("user_details"))
  782. # end of surrounding try-except
  783. # end of user_details view function for route /user_details/<int:i_user_id>/
  784. @bp.route("/css_sample", methods=["GET"])
  785. def css_sample():
  786. """CSS sample page - purely testing"""
  787. return redirect(url_for("main.index"))
  788. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  789. s_url = url_for("main.index")#, _external=True)
  790. return render_template("css_sample.html", js=True, base=s_base, url=s_url)
  791. # end of css_sample view function for route /css_sample
  792. @bp.route("/tenders/", methods=["GET", "POST"])
  793. @login_required
  794. @check_admin
  795. def tenders():
  796. """manage tenders available for assignment to records"""
  797. try:
  798. tender_form = TenderForm()
  799. remove_tender_form = TenderRemovalForm()
  800. if tender_form.validate_on_submit() and request.form.get("btn_save_tender", "")=="Save":
  801. i_tender_id, s_reference_number, s_description = (int(tender_form.hid_tender_id.data), str(tender_form.txt_reference_number.data), str(tender_form.txt_description.data))
  802. if i_tender_id > 0:
  803. o_tender = db.session.get(tbl_tenders, i_tender_id)
  804. if o_tender is not None:
  805. if db.session.query(tbl_tenders).filter(tbl_tenders.id!=i_tender_id, tbl_tenders.v_reference_number==s_reference_number).count()>0:
  806. flash("There is already another tender with the same reference number on record", "error")
  807. else:
  808. o_tender.v_reference_number = s_reference_number
  809. o_tender.v_description = s_description
  810. db.session.add(o_tender)
  811. db.session.commit()
  812. flash("Tender updated", "success")
  813. # end of making sure no other duplicates with same reference number
  814. else:
  815. flash("No tender record could be retrieved", "error")
  816. # end of checking if record existed
  817. else:
  818. if db.session.query(tbl_tenders.id).filter(tbl_tenders.v_reference_number==s_reference_number).count()<1:
  819. db.session.add(tbl_tenders(v_reference_number=s_reference_number, v_description=s_description))
  820. db.session.commit()
  821. flash("Tender inserted", "success")
  822. else:
  823. flash("Atender with the same reference number has already been recorded", "error")
  824. # end of making sure new tender is not a duplicate
  825. # end of checking if new or existing tender record
  826. elif remove_tender_form.validate_on_submit():
  827. i_tender_id = int(remove_tender_form.hid_remove_tender_id.data)
  828. o_tender = db.session.get(tbl_tenders, i_tender_id)
  829. if o_tender is not None:
  830. db.session.delete(o_tender)
  831. db.session.commit()
  832. db.session.execute(delete(tbl_tender_cvs).where(tbl_tender_cvs.i_tender_id==i_tender_id))
  833. db.session.commit()
  834. flash("Tender removed", "success")
  835. # end of checking if record existed
  836. # end of checking for form submission
  837. tender_form.hid_tender_id.data, tender_form.txt_reference_number.data, tender_form.txt_description.data = (0, "", "")
  838. remove_tender_form.hid_remove_tender_id.data = "0"
  839. i_page = request.form.get("hid_page", 1, type=int)
  840. q_tenders = db.session.query(tbl_tenders.id, tbl_tenders.v_reference_number, tbl_tenders.v_description).order_by(tbl_tenders.v_reference_number)
  841. pagination = q_tenders.paginate(page=i_page, per_page=20)
  842. l_tenders = list(map(dict_row, pagination.items))
  843. s_base = "base_bs.html" if Config.BOOTSTRAP else "base.html"
  844. s_url = url_for("main.tenders")#, _external=True)
  845. return render_template("tenders.html", js=True, base=s_base, url=s_url, tenders=l_tenders, form=tender_form, remove_form=remove_tender_form, paging=pagination)
  846. except Exception as exc:
  847. return render_template_string(end_user_debugging("tenders"))
  848. # end of surrounding try-except
  849. # end of tenders view function for route /tenders
  850. @bp.route("/tenders_list/", methods=["GET"])
  851. @login_required
  852. def tenders_list():
  853. """return list of tender entries"""
  854. try:
  855. l_out = []
  856. try:
  857. q_tenders = db.session.query(tbl_tenders.id, tbl_tenders.v_reference_number, tbl_tenders.v_description)
  858. if q_tenders.count()>0:
  859. q_tenders = q_tenders.order_by(tbl_tenders.v_reference_number)
  860. l_out = list(map(dict_row, q_tenders.all()))
  861. # end of checking result count of query
  862. except Exception as exc:
  863. print(extract_exc("list roles?"))
  864. # end of try-except
  865. return make_response(jsonify(l_out), 200)
  866. except Exception as exc:
  867. return render_template_string(end_user_debugging("tenders_list"))
  868. # end of surrounding try-except
  869. # end of tenders_list view function for route /tenders_list/
  870. @bp.route("/tender_details/<int:i_tender_id>/", methods=["GET"])
  871. @login_required
  872. def tender_details(i_tender_id):
  873. """retrieve tender details"""
  874. try:
  875. d_out = {}
  876. o_tender = db.session.get(tbl_tenders, i_tender_id)
  877. if o_tender is not None:
  878. d_out = dict_instance(o_tender)
  879. # end of checking if record retrieved
  880. return make_response(jsonify(d_out), 200)
  881. except Exception as exc:
  882. return render_template_string(end_user_debugging("tender_details"))
  883. # end of surrounding try-except
  884. # end of tender_details view function for route /tender_details/<int:i_tender_id>/