Coverage for app/controllers/admin/routes.py: 24%
393 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-06-27 14:39 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-06-27 14:39 +0000
1from flask import request, render_template, url_for, g, redirect
2from flask import flash, abort, jsonify, session, send_file, render_template, request
3from peewee import DoesNotExist, fn, IntegrityError
4from playhouse.shortcuts import model_to_dict
5import json
6from datetime import datetime
7import os
9from app import app
10from app.models.program import Program
11from app.models.event import Event
12from app.models.eventRsvp import EventRsvp
13from app.models.eventParticipant import EventParticipant
14from app.models.user import User
15from app.models.course import Course
16from app.models.courseInstructor import CourseInstructor
17from app.models.courseParticipant import CourseParticipant
18from app.models.eventTemplate import EventTemplate
19from app.models.activityLog import ActivityLog
20from app.models.eventRsvpLog import EventRsvpLog
21from app.models.attachmentUpload import AttachmentUpload
22from app.models.bonnerCohort import BonnerCohort
23from app.models.certification import Certification
24from app.models.user import User
25from app.models.term import Term
26from app.models.eventViews import EventView
27from app.models.courseStatus import CourseStatus
29from app.logic.userManagement import getAllowedPrograms, getAllowedTemplates
30from app.logic.createLogs import createActivityLog
31from app.logic.certification import getCertRequirements, updateCertRequirements
32from app.logic.utils import selectSurroundingTerms, getFilesFromRequest, getRedirectTarget, setRedirectTarget
33from app.logic.events import cancelEvent, deleteEvent, attemptSaveEvent, preprocessEventData, calculateRecurringEventFrequency, deleteEventAndAllFollowing, deleteAllRecurringEvents, getBonnerEvents,addEventView, getEventRsvpCount, copyRsvpToNewEvent, getCountdownToEvent
34from app.logic.participants import getParticipationStatusForTrainings, checkUserRsvp
35from app.logic.minor import getMinorInterest
36from app.logic.fileHandler import FileHandler
37from app.logic.bonner import getBonnerCohorts, makeBonnerXls, rsvpForBonnerCohort
38from app.logic.serviceLearningCourses import parseUploadedFile, saveCourseParticipantsToDatabase, unapprovedCourses, approvedCourses, getImportedCourses, getInstructorCourses, editImportedCourses
39from app.logic.spreadsheet import createSpreadsheet
41from app.controllers.admin import admin_bp
44@admin_bp.route('/admin/reports')
45def reports():
46 return render_template("/admin/reports.html")
48@admin_bp.route('/download')
49def download_file():
50 academic_year = request.args.get('academic_year', '2023-2024') # Default academic year if not provided
51 working_filepath = createSpreadsheet(academic_year)
52 absolute_filepath = os.path.abspath(working_filepath)
53 return send_file(absolute_filepath, as_attachment=True)
55@admin_bp.route('/switch_user', methods=['POST'])
56def switchUser():
57 if app.env == "production":
58 print(f"An attempt was made to switch to another user by {g.current_user.username}!")
59 abort(403)
61 print(f"Switching user from {g.current_user} to",request.form['newuser'])
62 session['current_user'] = model_to_dict(User.get_by_id(request.form['newuser']))
64 return redirect(request.referrer)
67@admin_bp.route('/eventTemplates')
68def templateSelect():
69 if g.current_user.isCeltsAdmin or g.current_user.isCeltsStudentStaff:
70 allprograms = getAllowedPrograms(g.current_user)
71 visibleTemplates = getAllowedTemplates(g.current_user)
72 return render_template("/events/template_selector.html",
73 programs=allprograms,
74 celtsSponsoredProgram = Program.get(Program.isOtherCeltsSponsored),
75 templates=visibleTemplates)
76 else:
77 abort(403)
80@admin_bp.route('/eventTemplates/<templateid>/<programid>/create', methods=['GET','POST'])
81def createEvent(templateid, programid):
82 if not (g.current_user.isAdmin or g.current_user.isProgramManagerFor(programid)):
83 abort(403)
85 # Validate given URL
86 program = None
87 try:
88 template = EventTemplate.get_by_id(templateid)
89 if programid:
90 program = Program.get_by_id(programid)
91 except DoesNotExist as e:
92 print("Invalid template or program id:", e)
93 flash("There was an error with your selection. Please try again or contact Systems Support.", "danger")
94 return redirect(url_for("admin.program_picker"))
96 # Get the data from the form or from the template
97 eventData = template.templateData
99 eventData['program'] = program
101 if request.method == "GET":
102 eventData['contactName'] = "CELTS Admin"
103 eventData['contactEmail'] = app.config['celts_admin_contact']
104 if program:
105 eventData['location'] = program.defaultLocation
106 if program.contactName:
107 eventData['contactName'] = program.contactName
108 if program.contactEmail:
109 eventData['contactEmail'] = program.contactEmail
111 # Try to save the form
112 if request.method == "POST":
113 eventData.update(request.form.copy())
114 try:
115 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
117 except Exception as e:
118 print("Error saving event:", e)
119 savedEvents = False
120 validationErrorMessage = "Unknown Error Saving Event. Please try again"
122 if savedEvents:
123 rsvpcohorts = request.form.getlist("cohorts[]")
124 for year in rsvpcohorts:
125 rsvpForBonnerCohort(int(year), savedEvents[0].id)
127 noun = (eventData['isRecurring'] == 'on' and "Events" or "Event") # pluralize
128 flash(f"{noun} successfully created!", 'success')
130 if program:
131 if len(savedEvents) > 1:
132 createActivityLog(f"Created a recurring event, <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a>, for {program.programName}, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}. The last event in the series will be on {datetime.strftime(savedEvents[-1].startDate, '%m/%d/%Y')}.")
133 else:
134 createActivityLog(f"Created <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a> for {program.programName}, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}.")
135 else:
136 createActivityLog(f"Created a non-program event, <a href=\"{url_for('admin.eventDisplay', eventId = savedEvents[0].id)}\">{savedEvents[0].name}</a>, with a start date of {datetime.strftime(eventData['startDate'], '%m/%d/%Y')}.")
138 return redirect(url_for("admin.eventDisplay", eventId = savedEvents[0].id))
139 else:
140 flash(validationErrorMessage, 'warning')
142 # make sure our data is the same regardless of GET or POST
143 preprocessEventData(eventData)
144 isProgramManager = g.current_user.isProgramManagerFor(programid)
146 futureTerms = selectSurroundingTerms(g.current_term, prevTerms=0)
148 requirements, bonnerCohorts = [], []
149 if eventData['program'] is not None and eventData['program'].isBonnerScholars:
150 requirements = getCertRequirements(Certification.BONNER)
151 bonnerCohorts = getBonnerCohorts(limit=5)
152 return render_template(f"/admin/{template.templateFile}",
153 template = template,
154 eventData = eventData,
155 futureTerms = futureTerms,
156 requirements = requirements,
157 bonnerCohorts = bonnerCohorts,
158 isProgramManager = isProgramManager)
161@admin_bp.route('/event/<eventId>/rsvp', methods=['GET'])
162def rsvpLogDisplay(eventId):
163 event = Event.get_by_id(eventId)
164 if g.current_user.isCeltsAdmin or (g.current_user.isCeltsStudentStaff and g.current_user.isProgramManagerFor(event.program)):
165 allLogs = EventRsvpLog.select(EventRsvpLog, User).join(User).where(EventRsvpLog.event_id == eventId).order_by(EventRsvpLog.createdOn.desc())
166 return render_template("/events/rsvpLog.html",
167 event = event,
168 allLogs = allLogs)
169 else:
170 abort(403)
172@admin_bp.route('/event/<eventId>/renew', methods=['POST'])
173def renewEvent(eventId):
174 try:
175 formData = request.form
176 try:
177 assert formData['timeStart'] < formData['timeEnd']
178 except AssertionError:
179 flash("End time must be after start time", 'warning')
180 return redirect(url_for('admin.eventDisplay', eventId = eventId))
182 try:
183 if formData.get('dateEnd'):
184 assert formData['dateStart'] < formData['dateEnd']
185 except AssertionError:
186 flash("End date must be after start date", 'warning')
187 return redirect(url_for('admin.eventDisplay', eventId = eventId))
190 priorEvent = model_to_dict(Event.get_by_id(eventId))
191 newEventDict = priorEvent.copy()
192 newEventDict.pop('id')
193 newEventDict.update({
194 'program': int(priorEvent['program']['id']),
195 'term': int(priorEvent['term']['id']),
196 'timeStart': formData['timeStart'],
197 'timeEnd': formData['timeEnd'],
198 'location': formData['location'],
199 'startDate': f'{formData["startDate"][-4:]}-{formData["startDate"][0:-5]}',
200 'endDate': f'{formData["endDate"][-4:]}-{formData["endDate"][0:-5]}',
201 'isRecurring': bool(priorEvent['recurringId'])
202 })
203 newEvent, message = attemptSaveEvent(newEventDict, renewedEvent = True)
204 if message:
205 flash(message, "danger")
206 return redirect(url_for('admin.eventDisplay', eventId = eventId))
208 copyRsvpToNewEvent(priorEvent, newEvent[0])
209 createActivityLog(f"Renewed {priorEvent['name']} as <a href='event/{newEvent[0].id}/view'>{newEvent[0].name}</a>.")
210 flash("Event successfully renewed.", "success")
211 return redirect(url_for('admin.eventDisplay', eventId = newEvent[0].id))
214 except Exception as e:
215 print("Error while trying to renew event:", e)
216 flash("There was an error renewing the event. Please try again or contact Systems Support.", 'danger')
217 return redirect(url_for('admin.eventDisplay', eventId = eventId))
221@admin_bp.route('/event/<eventId>/view', methods=['GET'])
222@admin_bp.route('/event/<eventId>/edit', methods=['GET','POST'])
223def eventDisplay(eventId):
224 pageViewsCount = EventView.select().where(EventView.event == eventId).count()
225 if request.method == 'GET' and request.path == f'/event/{eventId}/view':
226 viewer = g.current_user
227 event = Event.get_by_id(eventId)
228 addEventView(viewer,event)
229 # Validate given URL
230 try:
231 event = Event.get_by_id(eventId)
232 except DoesNotExist as e:
233 print(f"Unknown event: {eventId}")
234 abort(404)
236 notPermitted = not (g.current_user.isCeltsAdmin or g.current_user.isProgramManagerForEvent(event))
237 if 'edit' in request.url_rule.rule and notPermitted:
238 abort(403)
240 eventData = model_to_dict(event, recurse=False)
241 associatedAttachments = AttachmentUpload.select().where(AttachmentUpload.event == event)
242 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
244 image = None
245 picurestype = [".jpeg", ".png", ".gif", ".jpg", ".svg", ".webp"]
246 for attachment in associatedAttachments:
247 for extension in picurestype:
248 if (attachment.fileName.endswith(extension) and attachment.isDisplayed == True):
249 image = filepaths[attachment.fileName][0]
250 if image:
251 break
254 if request.method == "POST": # Attempt to save form
255 eventData = request.form.copy()
256 try:
257 savedEvents, validationErrorMessage = attemptSaveEvent(eventData, getFilesFromRequest(request))
259 except Exception as e:
260 print("Error saving event:", e)
261 savedEvents = False
262 validationErrorMessage = "Unknown Error Saving Event. Please try again"
265 if savedEvents:
266 rsvpcohorts = request.form.getlist("cohorts[]")
267 for year in rsvpcohorts:
268 rsvpForBonnerCohort(int(year), event.id)
270 flash("Event successfully updated!", "success")
271 return redirect(url_for("admin.eventDisplay", eventId = event.id))
272 else:
273 flash(validationErrorMessage, 'warning')
275 # make sure our data is the same regardless of GET and POST
276 preprocessEventData(eventData)
277 eventData['program'] = event.program
278 futureTerms = selectSurroundingTerms(g.current_term)
279 userHasRSVPed = checkUserRsvp(g.current_user, event)
280 filepaths = FileHandler(eventId=event.id).retrievePath(associatedAttachments)
281 isProgramManager = g.current_user.isProgramManagerFor(eventData['program'])
282 requirements, bonnerCohorts = [], []
284 if eventData['program'] and eventData['program'].isBonnerScholars:
285 requirements = getCertRequirements(Certification.BONNER)
286 bonnerCohorts = getBonnerCohorts(limit=5)
288 rule = request.url_rule
290 # Event Edit
291 if 'edit' in rule.rule:
292 return render_template("admin/createEvent.html",
293 eventData = eventData,
294 futureTerms=futureTerms,
295 event = event,
296 requirements = requirements,
297 bonnerCohorts = bonnerCohorts,
298 userHasRSVPed = userHasRSVPed,
299 isProgramManager = isProgramManager,
300 filepaths = filepaths)
301 # Event View
302 else:
303 # get text representations of dates for html
304 eventData['timeStart'] = event.timeStart.strftime("%-I:%M %p")
305 eventData['timeEnd'] = event.timeEnd.strftime("%-I:%M %p")
306 eventData['startDate'] = event.startDate.strftime("%m/%d/%Y")
307 eventCountdown = getCountdownToEvent(event)
310 # Identify the next event in a recurring series
311 if event.recurringId:
312 eventSeriesList = list(Event.select().where(Event.recurringId == event.recurringId)
313 .where((Event.isCanceled == False) | (Event.id == event.id))
314 .order_by(Event.startDate))
315 eventIndex = eventSeriesList.index(event)
316 if len(eventSeriesList) != (eventIndex + 1):
317 eventData["nextRecurringEvent"] = eventSeriesList[eventIndex + 1]
319 currentEventRsvpAmount = getEventRsvpCount(event.id)
321 userParticipatedTrainingEvents = getParticipationStatusForTrainings(eventData['program'], [g.current_user], g.current_term)
323 return render_template("eventView.html",
324 eventData=eventData,
325 event=event,
326 userHasRSVPed=userHasRSVPed,
327 programTrainings=userParticipatedTrainingEvents,
328 currentEventRsvpAmount=currentEventRsvpAmount,
329 isProgramManager=isProgramManager,
330 filepaths=filepaths,
331 image=image,
332 pageViewsCount=pageViewsCount,
333 eventCountdown=eventCountdown)
337@admin_bp.route('/event/<eventId>/cancel', methods=['POST'])
338def cancelRoute(eventId):
339 if g.current_user.isAdmin:
340 try:
341 cancelEvent(eventId)
342 return redirect(request.referrer)
344 except Exception as e:
345 print('Error while canceling event:', e)
346 return "", 500
348 else:
349 abort(403)
351@admin_bp.route('/event/<eventId>/delete', methods=['POST'])
352def deleteRoute(eventId):
353 try:
354 deleteEvent(eventId)
355 flash("Event successfully deleted.", "success")
356 return redirect(url_for("main.events", selectedTerm=g.current_term))
358 except Exception as e:
359 print('Error while canceling event:', e)
360 return "", 500
362@admin_bp.route('/event/<eventId>/deleteEventAndAllFollowing', methods=['POST'])
363def deleteEventAndAllFollowingRoute(eventId):
364 try:
365 deleteEventAndAllFollowing(eventId)
366 flash("Events successfully deleted.", "success")
367 return redirect(url_for("main.events", selectedTerm=g.current_term))
369 except Exception as e:
370 print('Error while canceling event:', e)
371 return "", 500
373@admin_bp.route('/event/<eventId>/deleteAllRecurring', methods=['POST'])
374def deleteAllRecurringEventsRoute(eventId):
375 try:
376 deleteAllRecurringEvents(eventId)
377 flash("Events successfully deleted.", "success")
378 return redirect(url_for("main.events", selectedTerm=g.current_term))
380 except Exception as e:
381 print('Error while canceling event:', e)
382 return "", 500
384@admin_bp.route('/makeRecurringEvents', methods=['POST'])
385def addRecurringEvents():
386 recurringEvents = calculateRecurringEventFrequency(preprocessEventData(request.form.copy()))
387 return json.dumps(recurringEvents, default=str)
390@admin_bp.route('/userProfile', methods=['POST'])
391def userProfile():
392 volunteerName= request.form.copy()
393 if volunteerName['searchStudentsInput']:
394 username = volunteerName['searchStudentsInput'].strip("()")
395 user=username.split('(')[-1]
396 return redirect(url_for('main.viewUsersProfile', username=user))
397 else:
398 flash(f"Please enter the first name or the username of the student you would like to search for.", category='danger')
399 return redirect(url_for('admin.studentSearchPage'))
401@admin_bp.route('/search_student', methods=['GET'])
402def studentSearchPage():
403 if g.current_user.isAdmin:
404 return render_template("/admin/searchStudentPage.html")
405 abort(403)
407@admin_bp.route('/addParticipants', methods = ['GET'])
408def addParticipants():
409 '''Renders the page, will be removed once merged with full page'''
411 return render_template('addParticipants.html',
412 title="Add Participants")
414@admin_bp.route('/activityLogs', methods = ['GET', 'POST'])
415def activityLogs():
416 if g.current_user.isCeltsAdmin:
417 allLogs = ActivityLog.select(ActivityLog, User).join(User).order_by(ActivityLog.createdOn.desc())
418 return render_template("/admin/activityLogs.html",
419 allLogs = allLogs)
420 else:
421 abort(403)
423@admin_bp.route("/deleteEventFile", methods=["POST"])
424def deleteEventFile():
425 fileData= request.form
426 eventfile=FileHandler(eventId=fileData["databaseId"])
427 eventfile.deleteFile(fileData["fileId"])
428 return ""
430@admin_bp.route("/uploadCourseParticipant", methods= ["POST"])
431def addCourseFile():
432 fileData = request.files['addCourseParticipants']
433 filePath = os.path.join(app.config["files"]["base_path"], fileData.filename)
434 fileData.save(filePath)
435 (session['cpPreview'], session['cpErrors']) = parseUploadedFile(filePath)
436 os.remove(filePath)
437 return redirect(url_for("admin.manageServiceLearningCourses"))
439@admin_bp.route('/manageServiceLearning', methods = ['GET', 'POST'])
440@admin_bp.route('/manageServiceLearning/<term>', methods = ['GET', 'POST'])
441def manageServiceLearningCourses(term=None):
443 """
444 The SLC management page for admins
445 """
446 if not g.current_user.isCeltsAdmin:
447 abort(403)
449 if request.method == 'POST' and "submitParticipant" in request.form:
450 saveCourseParticipantsToDatabase(session.pop('cpPreview', {}))
451 flash('Courses and participants saved successfully!', 'success')
452 return redirect(url_for('admin.manageServiceLearningCourses'))
454 manageTerm = Term.get_or_none(Term.id == term) or g.current_term
456 setRedirectTarget(request.full_path)
457 # retrieve and store the courseID of the imported course from a session variable if it exists.
458 # This allows us to export the courseID in the html and use it.
459 courseID = session.get("alterCourseId")
461 if courseID:
462 # delete courseID from the session if it was retrieved, for storage purposes.
463 session.pop("alterCourseId")
464 return render_template('/admin/manageServiceLearningFaculty.html',
465 courseInstructors = getInstructorCourses(),
466 unapprovedCourses = unapprovedCourses(manageTerm),
467 approvedCourses = approvedCourses(manageTerm),
468 importedCourses = getImportedCourses(manageTerm),
469 terms = selectSurroundingTerms(g.current_term),
470 term = manageTerm,
471 cpPreview = session.get('cpPreview', {}),
472 cpPreviewErrors = session.get('cpErrors', []),
473 courseID = courseID
474 )
476 return render_template('/admin/manageServiceLearningFaculty.html',
477 courseInstructors = getInstructorCourses(),
478 unapprovedCourses = unapprovedCourses(manageTerm),
479 approvedCourses = approvedCourses(manageTerm),
480 importedCourses = getImportedCourses(manageTerm),
481 terms = selectSurroundingTerms(g.current_term),
482 term = manageTerm,
483 cpPreview= session.get('cpPreview',{}),
484 cpPreviewErrors = session.get('cpErrors',[])
485 )
487@admin_bp.route('/admin/getSidebarInformation', methods=['GET'])
488def getSidebarInformation() -> str:
489 """
490 Get the count of unapproved courses and students interested in the minor for the current term
491 to display in the admin sidebar. It must be returned as a string to be received by the
492 ajax request.
493 """
494 unapprovedCoursesCount: int = len(unapprovedCourses(g.current_term))
495 interestedStudentsCount: int = len(getMinorInterest())
496 return {"unapprovedCoursesCount": unapprovedCoursesCount,
497 "interestedStudentsCount": interestedStudentsCount}
499@admin_bp.route("/deleteUploadedFile", methods= ["POST"])
500def removeFromSession():
501 try:
502 session.pop('cpPreview')
503 except KeyError:
504 pass
506 return ""
508@admin_bp.route('/manageServiceLearning/imported/<courseID>', methods = ['POST', 'GET'])
509def alterImportedCourse(courseID):
510 """
511 This route handles a GET and a POST request for the purpose of imported courses.
512 The GET request provides preexisting information of an imported course in a modal.
513 The POST request updates a specific imported course (course name, course abbreviation,
514 hours earned on completion, list of instructors) in the database with new information
515 coming from the imported courses modal.
516 """
517 if request.method == 'GET':
518 try:
519 targetCourse = Course.get_by_id(courseID)
520 targetInstructors = CourseInstructor.select().where(CourseInstructor.course == targetCourse)
522 try:
523 serviceHours = list(CourseParticipant.select().where(CourseParticipant.course_id == targetCourse.id))[0].hoursEarned
524 except IndexError: # If a course has no participant, IndexError will be raised
525 serviceHours = 20
527 courseData = model_to_dict(targetCourse, recurse=False)
528 courseData['instructors'] = [model_to_dict(instructor.user) for instructor in targetInstructors]
529 courseData['hoursEarned'] = serviceHours
531 return jsonify(courseData)
533 except DoesNotExist:
534 flash("Course not found")
535 return jsonify({"error": "Course not found"}), 404
537 if request.method == 'POST':
538 # Update course information in the database
539 courseData = request.form.copy()
540 editImportedCourses(courseData)
541 session['alterCourseId'] = courseID
543 return redirect(url_for("admin.manageServiceLearningCourses", term=courseData['termId']))
546@admin_bp.route("/manageBonner")
547def manageBonner():
548 if not g.current_user.isCeltsAdmin:
549 abort(403)
551 return render_template("/admin/bonnerManagement.html",
552 cohorts=getBonnerCohorts(),
553 events=getBonnerEvents(g.current_term),
554 requirements=getCertRequirements(certification=Certification.BONNER))
556@admin_bp.route("/bonner/<year>/<method>/<username>", methods=["POST"])
557def updatecohort(year, method, username):
558 if not g.current_user.isCeltsAdmin:
559 abort(403)
561 try:
562 user = User.get_by_id(username)
563 except:
564 abort(500)
566 if method == "add":
567 try:
568 BonnerCohort.create(year=year, user=user)
569 flash(f"Successfully added {user.fullName} to {year} Bonner Cohort.", "success")
570 except IntegrityError as e:
571 # if they already exist, ignore the error
572 flash(f'Error: {user.fullName} already added.', "danger")
573 pass
575 elif method == "remove":
576 BonnerCohort.delete().where(BonnerCohort.user == user, BonnerCohort.year == year).execute()
577 flash(f"Successfully removed {user.fullName} from {year} Bonner Cohort.", "success")
578 else:
579 flash(f"Error: {user.fullName} can't be added.", "danger")
580 abort(500)
582 return ""
584@admin_bp.route("/bonnerxls")
585def bonnerxls():
586 if not g.current_user.isCeltsAdmin:
587 abort(403)
589 newfile = makeBonnerXls()
590 return send_file(open(newfile, 'rb'), download_name='BonnerStudents.xlsx', as_attachment=True)
592@admin_bp.route("/saveRequirements/<certid>", methods=["POST"])
593def saveRequirements(certid):
594 if not g.current_user.isCeltsAdmin:
595 abort(403)
597 newRequirements = updateCertRequirements(certid, request.get_json())
599 return jsonify([requirement.id for requirement in newRequirements])
602@admin_bp.route("/displayEventFile", methods=["POST"])
603def displayEventFile():
604 fileData = request.form
605 eventfile = FileHandler(eventId=fileData["id"])
606 isChecked = fileData.get('checked') == 'true'
607 eventfile.changeDisplay(fileData['id'], isChecked)
608 return ""