예제 #1
0
 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()
예제 #2
0
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)
예제 #3
0
 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
예제 #4
0
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)
예제 #5
0
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)
예제 #6
0
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)
예제 #7
0
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
예제 #8
0
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)
예제 #9
0
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)
예제 #10
0
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
예제 #11
0
 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()
예제 #12
0
 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()
예제 #13
0
 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()
예제 #14
0
    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