15.3.39. crate_anon.crateweb.research.models


Copyright (C) 2015-2018 Rudolf Cardinal (rudolf@pobox.com).

This file is part of CRATE.

CRATE is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

CRATE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with CRATE. If not, see <http://www.gnu.org/licenses/>.


class crate_anon.crateweb.research.models.Highlight(*args, **kwargs)[source]

Represents the highlighting of a query.

exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.research.models.PatientExplorer(*args, **kwargs)[source]

Class to explore the research database on a per-patient basis.

exception DoesNotExist
exception MultipleObjectsReturned
data_finder_excel

Performs a SELECT COUNT(*) Returns (fieldnames, rows).

delete_if_permitted() → None[source]

If a PE has been audited, it isn’t properly deleted.

static get_executed_cursor(sql: str, args: List[Any] = None) → django.db.backends.utils.CursorWrapper[source]

Get cursor with a query executed

get_xlsx_binary() → bytes[source]

Other notes:

save(*args, **kwargs) → None[source]

Custom save method. Ensures that only one PatientExplorer has active == True for a given user. Also sets the hash.

class crate_anon.crateweb.research.models.PatientExplorerAudit(*args, **kwargs)[source]

Audit log for a PatientExplorer.

exception DoesNotExist
exception MultipleObjectsReturned
class crate_anon.crateweb.research.models.PidLookup(*args, **kwargs)[source]

Lookup class for secret RID-to-PID conversion. Used via one or other of the ‘secret’ database connections. Intended for READ-ONLY access to that table.

Since we have fixed the tablenames for the anonymiser, we remove the settings.SECRET_MAP option. See PatientInfo in crate_anon/anonymise/models.py. Moreover, we fix the maximum length, regardless of the specifics of the config used.

Use as e.g. Lookup(pid=XXX)

exception DoesNotExist
exception MultipleObjectsReturned
save(*args, **kwargs) → None[source]

Save the current instance. Override this in a subclass if you want to control the saving process.

The ‘force_insert’ and ‘force_update’ parameters can be used to insist that the “save” must be an SQL insert or update (or equivalent for non-SQL backends), respectively. Normally, they should not be set.

class crate_anon.crateweb.research.models.Query(*args, **kwargs)[source]

Class to query the research database.

exception DoesNotExist
exception MultipleObjectsReturned
delete_if_permitted() → None[source]

If a query has been audited, it isn’t properly deleted.

dictfetchall() → List[Dict[str, Any]][source]

Generates all results as a list of OrderedDicts.

get_executed_cursor(sql_append_raw: str = None) → django.db.backends.utils.CursorWrapper[source]

Get cursor with a query executed

get_sql_args_for_django() → Tuple[str, Union[List[Any], NoneType]][source]

Get sql/args in a format suitable for Django, with %s placeholders, or as escaped raw SQL.

save(*args, **kwargs) → None[source]

Custom save method. Ensures that only one Query has active == True for a given user. Also sets the hash.

class crate_anon.crateweb.research.models.QueryAudit(*args, **kwargs)[source]

Audit log for a query.

exception DoesNotExist
exception MultipleObjectsReturned
crate_anon.crateweb.research.models.gen_excel_row_elements(worksheet: openpyxl.worksheet.worksheet.Worksheet, row: Iterable) → Generator[[Any, NoneType], NoneType][source]

Given an Excel worksheet row, generate individual cell contents, cell by cell.

Reasons for this function:

  1. Need a tuple/list/generator, as openpyxl checks its types manually.
  • We want to have a Worksheet object from openpyxl, and say something like

    ws.append(row)
    

    where “row” has come from a database query.

  • However, openpyxl doesn’t believe in duck-typing; see Worksheet.append() in openpyxl/worksheet/worksheet.py. So sometimes the plain append works (e.g. from MySQL results), but sometimes it fails, e.g. when the row is of type pyodbc.Row.

  • So we must coerce it to a tuple, list, or generator.

  • A generator will be the most efficient.

  1. If a string fails certain checks, openpyxl will raise an IllegalCharacterError exception. We need to work around that. We’ll use the “forgiveness, not permission” maxim. Specifically, it dislikes strings matching its ILLEGAL_CHARACTERS_RE, which contains unprintable low characters matching this:

    r'[\000-\010]|[\013-\014]|[\016-\037]'
    

    Note the use of octal; \037 is decimal 31.

    openpyxl gets to its Cell.check_string() function for these types:

    STRING_TYPES = (basestring, unicode, bytes)
    

    In Python 3, this means (str, str, bytes). So we should check str and bytes. (For bytes, we’ll follow its method of converting to str in the encoding of the worksheet’s choice.)

crate_anon.crateweb.research.models.hack_django_pyodbc_azure_cursorwrapper() → None[source]

Monkey-patch part of the sql_server.pyodbc library from django-pyodbc-azure. It replaces the fetchone() method with a version that doesn’t call cursor.nextset() automatically.

It looks like this becomes unnecessary in django-pyodbc-azure==2.0.6.1 or similar, because the call to ``cursor.nextset()`` is now only performed ``if not self.connection.supports_mars``.

Notes

  • I thought I wanted to modify an instance, not a class (https://tryolabs.com/blog/2013/07/05/run-time-method-patching-python/).
  • To modify a class, we do SomeClass.method = newmethod.
  • But to modify an instance, we use instance.method = types.MethodType(newmethod, instance).
  • However, it turned out the instance was actually part of a long chain of cursor wrappers, including the Django debug toolbar. Classes included debug_toolbar.panels.sql.tracking.NormalCursorWrapper; django.db.backends.utils.CursorDebugWrapper. And in any case, modifying the class is a sensible thing.
crate_anon.crateweb.research.models.replacement_sqlserver_pyodbc_cursorwrapper_fetchone(self) → List[Any][source]

A function to replace CursorWrapper.fetchone() in sql_server/pyodbc/base.py from django-pyodbc-azure. This replacement function does not call cursor.nextset().