def apply_filters(self, form): """Apply filters from details in the CGI form.""" filter_surname = ws.get_cgi_parameter_str_or_none(form, PARAM.SURNAME) if filter_surname: self.filter_surname = filter_surname.upper() filter_forename = ws.get_cgi_parameter_str_or_none(form, PARAM.FORENAME) if filter_forename: self.filter_forename = filter_forename.upper() dt = ws.get_cgi_parameter_datetime(form, PARAM.DOB) if dt: self.filter_dob_iso8601 = cc_dt.format_datetime( dt, DATEFORMAT.ISO8601_DATE_ONLY) # NB date only filter_sex = ws.get_cgi_parameter_str_or_none(form, PARAM.SEX) if filter_sex: self.filter_sex = filter_sex.upper() which_idnum = ws.get_cgi_parameter_int(form, PARAM.WHICH_IDNUM) idnum_value = ws.get_cgi_parameter_int(form, PARAM.IDNUM_VALUE) if (which_idnum and idnum_value is not None and which_idnum >= 1 and which_idnum <= NUMBER_OF_IDNUMS): self.clear_filter_idnums() # Only filter on one ID at a time. setattr(self, "filter_idnum" + str(which_idnum), idnum_value) filter_task = ws.get_cgi_parameter_str_or_none(form, PARAM.TASK) if filter_task: self.filter_task = filter_task filter_complete = ws.get_cgi_parameter_bool_or_none(form, PARAM.COMPLETE) if filter_complete is not None: self.filter_complete = filter_complete filter_include_old_versions = ws.get_cgi_parameter_bool_or_none( form, PARAM.INCLUDE_OLD_VERSIONS) if filter_include_old_versions is not None: self.filter_include_old_versions = filter_include_old_versions filter_device = ws.get_cgi_parameter_str_or_none(form, PARAM.DEVICE) if filter_device: self.filter_device = filter_device filter_user = ws.get_cgi_parameter_str_or_none(form, PARAM.USER) if filter_user: self.filter_user = filter_user dt = ws.get_cgi_parameter_datetime(form, PARAM.START_DATETIME) if dt: self.filter_start_datetime_iso8601 = cc_dt.format_datetime( dt, DATEFORMAT.ISO8601) dt = ws.get_cgi_parameter_datetime(form, PARAM.END_DATETIME) if dt: self.filter_end_datetime_iso8601 = cc_dt.format_datetime( dt, DATEFORMAT.ISO8601) filter_text = ws.get_cgi_parameter_str_or_none(form, PARAM.TEXT) if filter_text: self.filter_text = filter_text self.reset_pagination()
def get_export_filename(patient_spec_if_anonymous, patient_spec, filename_spec, task_format, is_anonymous=False, surname=None, forename=None, dob=None, sex=None, idnums=[None]*NUMBER_OF_IDNUMS, idshortdescs=[""]*NUMBER_OF_IDNUMS, creation_datetime=None, basetable=None, serverpk=None): """Get filename, for file exports/transfers.""" if idnums is None: idnums = [None]*NUMBER_OF_IDNUMS if idshortdescs is None: idshortdescs = [""]*NUMBER_OF_IDNUMS d = dict( surname=surname or "", forename=forename or "", dob=( cc_dt.format_datetime(dob, DATEFORMAT.FILENAME_DATE_ONLY, "") if dob else "" ), sex=sex or "", ) all_id_components = [] for n in range(1, NUMBER_OF_IDNUMS + 1): i = n - 1 nstr = str(n) has_num = idnums[i] is not None has_desc = bool(idshortdescs[i]) d["idshortdesc"+nstr] = idshortdescs[i] if has_num and has_desc else "" d["idnum"+nstr] = str(idnums[i]) if has_num else "" if has_num and has_desc: all_id_components.append(idshortdescs[i] + "-" + str(idnums[i])) d["allidnums"] = "_".join(all_id_components) if is_anonymous: patient = patient_spec_if_anonymous else: patient = unicode(patient_spec).format(**d) d.update(dict( patient=patient, created=cc_dt.format_datetime(creation_datetime, DATEFORMAT.FILENAME, ""), now=cc_dt.format_datetime(cc_dt.get_now_localtz(), DATEFORMAT.FILENAME), tasktype=str(basetable or ""), serverpk=str(serverpk or ""), filetype=task_format.lower(), anonymous=patient_spec_if_anonymous if is_anonymous else "", )) return convert_string_for_filename( unicode(filename_spec).format(**d), allow_paths=True)
def set_always(self): """Set the things we set every time the script is invoked (time!).""" self.NOW_LOCAL_TZ = cc_dt.get_now_localtz() # ... we want nearly all our times offset-aware # ... http://stackoverflow.com/questions/4530069 self.NOW_UTC_WITH_TZ = cc_dt.convert_datetime_to_utc(self.NOW_LOCAL_TZ) self.NOW_UTC_NO_TZ = cc_dt.convert_datetime_to_utc_notz(self.NOW_LOCAL_TZ) self.NOW_LOCAL_TZ_ISO8601 = cc_dt.format_datetime(self.NOW_LOCAL_TZ, DATEFORMAT.ISO8601) self.TODAY = datetime.date.today() # fetches the local date
def send_analytics_if_necessary(): """Send analytics to the CamCOPS base server, if required. If analytics reporting is enabled, and analytics have not been sent recently, collate and send them to the CamCOPS base server in Cambridge, UK. """ if not pls.SEND_ANALYTICS: # User has disabled analytics reporting. return lastSentVar = cc_storedvar.ServerStoredVar("lastAnalyticsSentAt", "text", None) lastSentVal = lastSentVar.getValue() if lastSentVal: elapsed = pls.NOW_UTC_WITH_TZ - cc_dt.get_datetime_from_string( lastSentVal) if elapsed < ANALYTICS_PERIOD: # We sent analytics recently. return # Compile analytics now_as_utc_iso_string = cc_dt.format_datetime(pls.NOW_UTC_WITH_TZ, DATEFORMAT.ISO8601) (table_names, record_counts) = get_all_tables_with_record_counts() # This is what's sent: d = { "source": "server", "now": now_as_utc_iso_string, "camcops_version": str(cc_version.CAMCOPS_SERVER_VERSION), "server": pls.SERVER_NAME, "table_names": ",".join(table_names), "record_counts": ",".join([str(x) for x in record_counts]), } # The HTTP_HOST variable might provide some extra information, but is # per-request rather than per-server, making analytics involving it that # bit more intrusive for little extra benefit, so let's not send it. # See http://stackoverflow.com/questions/2297403 for details. # Send it. encoded_dict = urllib.urlencode(d) request = urllib2.Request(ANALYTICS_URL, encoded_dict) try: urllib2.urlopen(request, timeout=ANALYTICS_TIMEOUT_MS) # connection = urllib2.urlopen(request, timeout=ANALYTICS_TIMEOUT_MS) # response = connection.read() # we don't care except (urllib2.URLError, urllib2.HTTPError): # something broke; try again next time logger.info("Failed to send analytics to {}".format(ANALYTICS_URL)) return # Store current time as last-sent time logger.debug("Analytics sent.") lastSentVar.setValue(now_as_utc_iso_string)
def forcibly_preserve_special_notes(device_id): """WRITES TO DATABASE.""" new_era = cc_dt.format_datetime(pls.NOW_UTC_NO_TZ, DATEFORMAT.ERA) query = """ UPDATE _special_notes SET era = ? WHERE device = ? AND era = '{now}' """.format(now=ERA_NOW) args = [ new_era, device_id ] pls.db.db_exec(query, *args)
def forcibly_preserve_client_table(table, device_id, username): """WRITES TO DATABASE.""" new_era = cc_dt.format_datetime(pls.NOW_UTC_NO_TZ, DATEFORMAT.ERA) query = """ UPDATE {table} SET _era = ?, _forcibly_preserved = 1, _preserving_user = ? WHERE _device = ? AND _era = '{now}' """.format(table=table, now=ERA_NOW) args = [ new_era, username, device_id ] pls.db.db_exec(query, *args)
def provide_report(session, form): """Extracts report type, report parameters, and output type from the CGI form; offers up the results in the chosen format.""" # Which report? report_id = ws.get_cgi_parameter_str(form, PARAM.REPORT_ID) report = get_report_instance(report_id) if not report: return cc_html.fail_with_error_stay_logged_in("Invalid report_id") # What output type? outputtype = ws.get_cgi_parameter_str(form, PARAM.OUTPUTTYPE) if outputtype is not None: outputtype = outputtype.lower() if (outputtype != VALUE.OUTPUTTYPE_HTML and outputtype != VALUE.OUTPUTTYPE_TSV): return cc_html.fail_with_error_stay_logged_in("Unknown outputtype") # Get parameters params = get_params_from_form(report.get_param_spec_list(), form) # Get query details rows, descriptions = report.get_rows_descriptions(**params) if rows is None or descriptions is None: return cc_html.fail_with_error_stay_logged_in( "Report failed to return a list of descriptions/results") if outputtype == VALUE.OUTPUTTYPE_TSV: filename = ( "CamCOPS_" + report.get_report_id() + "_" + cc_dt.format_datetime(pls.NOW_LOCAL_TZ, DATEFORMAT.FILENAME) + ".tsv" ) return ws.tsv_result(tsv_from_query(rows, descriptions), [], filename) else: # HTML html = pls.WEBSTART + u""" {} <h1>{}</h1> """.format( session.get_current_user_html(), report.get_report_title(), ) + ws.html_table_from_query(rows, descriptions) + cc_html.WEBEND return html
def clear_dummy_login_failures_if_necessary(): """Clear dummy login failures if we haven't done so for a while. Not too often! See CLEAR_DUMMY_LOGIN_FREQUENCY_DAYS. """ lastClearedVar = cc_storedvar.ServerStoredVar( "lastDummyLoginFailureClearanceAt", "text", None) lastClearedVal = lastClearedVar.getValue() if lastClearedVal: elapsed = pls.NOW_UTC_WITH_TZ - cc_dt.get_datetime_from_string( lastClearedVal) if elapsed < CLEAR_DUMMY_LOGIN_PERIOD: # We cleared it recently. return clear_login_failures_for_nonexistent_users() logger.debug("Dummy login failures cleared.") now_as_utc_iso_string = cc_dt.format_datetime(pls.NOW_UTC_WITH_TZ, DATEFORMAT.ISO8601) lastClearedVar.setValue(now_as_utc_iso_string)
def manually_erase_record_object_and_save(obj, table, fields, username): """Manually erases a standard record and marks it so erased. The object remains _current, as a placeholder, but its contents are wiped. WRITES TO DATABASE.""" # ------------------------------------------------------------------------- # DELAYED IMPORTS # ------------------------------------------------------------------------- import cc_task if obj._pk is None or obj._era == ERA_NOW: return standard_task_fields = [x["name"] for x in cc_task.STANDARD_TASK_FIELDSPECS] erasure_fields = [ x for x in fields if x not in standard_task_fields ] rnc_db.blank_object(obj, erasure_fields) obj._current = False obj._manually_erased = True obj._manually_erased_at = cc_dt.format_datetime(pls.NOW_LOCAL_TZ, DATEFORMAT.ISO8601) obj._manually_erasing_user = username pls.db.update_object_in_db(obj, table, fields)
def manage_users(session): """HTML to view/edit users.""" allusers = pls.db.fetch_all_objects_from_db(User, User.TABLENAME, User.FIELDS, True) allusers = sorted(allusers, key=lambda k: k.user) output = pls.WEBSTART + u""" {} <h1>Manage users</h1> <ul> <li><a href="{}">Add user</a></li> </ul> <table> <tr> <th>User name</th> <th>Actions</th> <th>Locked out?</th> <th>Last password change (UTC)</th> <th>May use web viewer?</th> <th>May view other users’ records?</th> <th>Sees all patients’ records when unfiltered?</th> <th>May upload data?</th> <th>May manage users?</th> <th>May register tablet devices?</th> <th>May use webstorage?</th> <th>May dump data?</th> <th>May run reports?</th> <th>May add notes?</th> <th>Click to delete use</th> </tr> """.format( session.get_current_user_html(), cc_html.get_generic_action_url(ACTION.ASK_TO_ADD_USER), ) + cc_html.WEBEND for u in allusers: if u.is_locked_out(): enableuser = "******".format( get_url_enable_user(u.user) ) lockedmsg = "Yes, until {}".format(cc_dt.format_datetime( u.locked_out_until(), DATEFORMAT.ISO8601 )) else: enableuser = "" lockedmsg = "No" output += u""" <tr> <td>{username}</td> <td> <a href="{url_edit}">Edit permissions</a> | <a href="{url_changepw}">Change password</a> {enableuser} </td> <td>{lockedmsg}</td> <td>{lastpwchange}</td> <td>{may_use_webviewer}</td> <td>{may_view_other_users_records}</td> <td>{view_all_patients_when_unfiltered}</td> <td>{may_upload}</td> <td>{superuser}</td> <td>{may_register_devices}</td> <td>{may_use_webstorage}</td> <td>{may_dump_data}</td> <td>{may_run_reports}</td> <td>{may_add_notes}</td> <td><a href="{url_delete}">Delete user {username}</a></td> </tr> """.format( url_edit=get_url_edit_user(u.user), url_changepw=cc_html.get_url_enter_new_password(u.user), enableuser=enableuser, lockedmsg=lockedmsg, lastpwchange=ws.webify(u.last_password_change_utc), may_use_webviewer=cc_html.get_yes_no(u.may_use_webviewer), may_view_other_users_records=cc_html.get_yes_no( u.may_view_other_users_records), view_all_patients_when_unfiltered=cc_html.get_yes_no( u.view_all_patients_when_unfiltered), may_upload=cc_html.get_yes_no(u.may_upload), superuser=cc_html.get_yes_no(u.superuser), may_register_devices=cc_html.get_yes_no(u.may_register_devices), may_use_webstorage=cc_html.get_yes_no(u.may_use_webstorage), may_dump_data=cc_html.get_yes_no(u.may_dump_data), may_run_reports=cc_html.get_yes_no(u.may_run_reports), may_add_notes=cc_html.get_yes_no(u.may_add_notes), url_delete=get_url_ask_delete_user(u.user), username=u.user, ) output += u""" </table> """ + cc_html.WEBEND return output
def agree_terms(self): """Mark the user as having agreed to the terms/conditions of use now.""" self.when_agreed_terms_of_use = cc_dt.format_datetime( pls.NOW_LOCAL_TZ, DATEFORMAT.ISO8601) self.save()
def apply_filter_end_datetime(self, form): """Apply the end date filter.""" dt = ws.get_cgi_parameter_datetime(form, PARAM.END_DATETIME) self.filter_end_datetime_iso8601 = cc_dt.format_datetime( dt, DATEFORMAT.ISO8601) self.reset_pagination()
def apply_filter_dob(self, form): """Apply the DOB filter.""" dt = ws.get_cgi_parameter_datetime(form, PARAM.DOB) self.filter_dob_iso8601 = cc_dt.format_datetime( dt, DATEFORMAT.ISO8601_DATE_ONLY) # NB date only self.reset_pagination()
def get_current_filter_html(self): """HTML showing current filters and offering ways to set them.""" # Consider also multiple buttons in a single form: # http://stackoverflow.com/questions/942772 # ... might allow "apply all things entered here" button # ... HOWEVER, I think this would break the ability to press Enter # after entering information in any box (which is nice). found_one = False filters = [] id_filter_values = [] id_filter_descs = [] for n in range(1, NUMBER_OF_IDNUMS + 1): nstr = str(n) id_filter_values.append(getattr(self, "filter_idnum" + nstr)) id_filter_descs.append(pls.get_id_desc(n)) id_filter_value = None id_filter_name = "ID number" for index, value in enumerate(id_filter_values): if value is not None: id_filter_value = value id_filter_name = id_filter_descs[index] which_idnum_temp = u""" {picker} <input type="number" name="{PARAM.IDNUM_VALUE}"> """.format( picker=cc_html.get_html_which_idnum_picker(PARAM.WHICH_IDNUM), PARAM=PARAM, ) found_one = get_filter_html( id_filter_name, id_filter_value, ACTION.CLEAR_FILTER_IDNUMS, which_idnum_temp, ACTION.APPLY_FILTER_IDNUMS, filters ) or found_one found_one = get_filter_html( "Surname", self.filter_surname, ACTION.CLEAR_FILTER_SURNAME, """<input type="text" name="{}">""".format(PARAM.SURNAME), ACTION.APPLY_FILTER_SURNAME, filters ) or found_one found_one = get_filter_html( "Forename", self.filter_forename, ACTION.CLEAR_FILTER_FORENAME, """<input type="text" name="{}">""".format(PARAM.FORENAME), ACTION.APPLY_FILTER_FORENAME, filters ) or found_one found_one = get_filter_html( "Date of birth", cc_dt.format_datetime(self.get_filter_dob(), DATEFORMAT.LONG_DATE), ACTION.CLEAR_FILTER_DOB, """<input type="date" name="{}">""".format(PARAM.DOB), ACTION.APPLY_FILTER_DOB, filters ) or found_one found_one = get_filter_html( "Sex", self.filter_sex, ACTION.CLEAR_FILTER_SEX, cc_html.get_html_sex_picker(param=PARAM.SEX, selected=self.filter_sex, offer_all=True), ACTION.APPLY_FILTER_SEX, filters ) or found_one found_one = get_filter_html( "Task type", self.filter_task, ACTION.CLEAR_FILTER_TASK, cc_task.get_task_filter_dropdown(self.filter_task), ACTION.APPLY_FILTER_TASK, filters ) or found_one found_one = get_filter_html( "Task completed", cc_html.get_yes_no_none(self.filter_complete), ACTION.CLEAR_FILTER_COMPLETE, """ <select name="{PARAM.COMPLETE}"> <option value="">(all)</option> <option value="1"{selected_1}>Complete</option> <option value="0"{selected_0}>Incomplete</option> </select> """.format(PARAM=PARAM, selected_1=ws.option_selected(self.filter_complete, 1), selected_0=ws.option_selected(self.filter_complete, 0)), ACTION.APPLY_FILTER_COMPLETE, filters ) or found_one found_one = get_filter_html( "Include old (overwritten) versions", cc_html.get_yes_no_none(self.filter_include_old_versions), ACTION.CLEAR_FILTER_INCLUDE_OLD_VERSIONS, """ <select name="{PARAM.INCLUDE_OLD_VERSIONS}"> <option value="">(exclude)</option> <option value="1"{y}>Include</option> <option value="0"{n}>Exclude</option> </select> """.format(PARAM=PARAM, y=ws.option_selected(self.filter_include_old_versions, 1), n=ws.option_selected(self.filter_include_old_versions, 0)), ACTION.APPLY_FILTER_INCLUDE_OLD_VERSIONS, filters ) or found_one found_one = get_filter_html( "Tablet device", self.filter_device, ACTION.CLEAR_FILTER_DEVICE, cc_device.get_device_filter_dropdown(self.filter_device), ACTION.APPLY_FILTER_DEVICE, filters ) or found_one found_one = get_filter_html( "Adding user", self.filter_user, ACTION.CLEAR_FILTER_USER, """<input type="text" name="{}">""".format(PARAM.USER), ACTION.APPLY_FILTER_USER, filters ) or found_one found_one = get_filter_html( "Start date (UTC)", cc_dt.format_datetime(self.get_filter_start_datetime(), DATEFORMAT.LONG_DATE), ACTION.CLEAR_FILTER_START_DATETIME, """<input type="date" name="{}">""".format(PARAM.START_DATETIME), ACTION.APPLY_FILTER_START_DATETIME, filters ) or found_one found_one = get_filter_html( "End date (UTC)", cc_dt.format_datetime(self.get_filter_end_datetime(), DATEFORMAT.LONG_DATE), ACTION.CLEAR_FILTER_END_DATETIME, """<input type="date" name="{}">""".format(PARAM.END_DATETIME), ACTION.APPLY_FILTER_END_DATETIME, filters ) or found_one found_one = get_filter_html( "Text contents", ws.webify(self.filter_text), ACTION.CLEAR_FILTER_TEXT, """<input type="text" name="{}">""".format(PARAM.TEXT), ACTION.APPLY_FILTER_TEXT, filters ) or found_one clear_filter_html = u""" <input type="submit" name="{ACTION.CLEAR_FILTERS}" value="Clear all filters"> <br> """.format( ACTION=ACTION, ) no_filters_applied = "<p><b><i>No filters applied</i></b></p>" html = u""" <form class="filter" method="POST" action="{script}"> <input type="hidden" name="{PARAM.ACTION}" value="{ACTION.FILTER}"> <input type="submit" class="important" name="{ACTION.APPLY_FILTERS}" value="Apply new filters"> <br> <!-- First submit button is default on pressing Enter, which is why the Apply button is at the top of the form --> {clearbutton} {filters} </form> """.format( script=pls.SCRIPT_NAME, ACTION=ACTION, PARAM=PARAM, clearbutton=clear_filter_html if found_one else no_filters_applied, filters="".join(filters), ) return html