Utilities
Common utilities
- megforms.utils.itersubclasses(cls)
Generator over all subclasses of a given class, in depth first order.
>>> list(itersubclasses(int)) == [bool] True >>> class A(object): pass >>> class B(A): pass >>> class C(A): pass >>> class D(B,C): pass >>> class E(D): pass >>> >>> for cls in itersubclasses(A): ... print(cls.__name__) B D E C >>> # get ALL (new-style) classes currently defined >>> [cls.__name__ for cls in itersubclasses(object)] ['type', ...'tuple', ...]
http://code.activestate.com/recipes/576949-find-all-subclasses-of-a-given-class/
- megforms.utils.get_observation_models() Tuple[ObservationModel]
Returns tuple of all subclasses of BaseAuditModel
- megforms.utils.get_subform_models() Iterable[type[BaseSubObservation]]
Iterates over all subform models (models representing sub observations), including
CustomSubObservationInstance, but excluding abstract models (base HIQA and IPC subobservation models)
- megforms.utils.first(collection: Iterable[T]) T | None
Returns first element of given iterable, even if its a set or an empty collection
- megforms.utils.get_observation_model_paths() set[str]
Returns an iterable of strings representing valid python paths for observation models. Use this to filter out invalid AuditForms from your queryset
- megforms.utils.send_mail(subject, body_text, addr_from, addr_to, reply_to: list[str] = None, fail_silently=False, attachments=None, body_html=None, connection=None, headers=None, cc=None)
Sends a multipart email containing text and html versions with CC. Copy of Lib/site-packages/email_extras/utils.py:28 but with added CC and without encryption.
- megforms.utils.get_dotted_path(cls: type | Callable) str
Returns python dotted path for class
- megforms.utils.get_func_name(func: type | Callable) str
Based on a utility function from OpenCensus. Return a name which includes the module name and function name.
- megforms.utils.has_subforms(observation_model: ObservationModel) bool
Tells whether given observation model has hardcoded subforms
- megforms.utils.common_public_report_widgets(method)
Prepend common widgets to return value of decorated method
- megforms.utils.get_last_modified_date(qs: QuerySet, modified_field_name: str = 'modified') datetime | None
Gets last modified date for the qs of objects Returns modification date of last modified item
- Parameters:
qs – the queryset of any model containing a date field
modified_field_name – a datetime field representing when field was last modified
- class megforms.utils.DefaultDateValue(*args, **kwargs)
A callable that can be used to set default date in a django field relative to date of model creation. For instance, use this to set date 2 weeks ahead:
DefaultDateValue(timezone.timedelta(days=14))- Parameters:
timedelta – timedelta added on top of from_date to create the default date. Use a negative timedelta to subtract instead of adding
from_date – leave empty to use current date at the time this class is called, or hardcode a datetime
- deconstruct()
Return a 3-tuple of class import path, positional arguments, and keyword arguments.
- megforms.utils.autonomous_transaction(var_name='autonomuos')
Allows saving objects inside atomic transaction independently of the transaction rollback/commit. Wrapped function must be a view. Wrap in method_decorator to use with class based views
- Parameters:
var_name – name of the variable storing the object(s) to be saved. Variable needs to be attached to the request object.
- megforms.utils.FilteredRelatedListFilter(qs_or_callable)
Wrapper for RelatedFieldListFilter that shows a subset of items
- megforms.utils.request_institution_getter(request: HttpRequest) InstitutionQuerySet
Returns institution choices based on request returns empty QS if user is not an auditor or not logged in
- class megforms.utils.PermissionValidator
Django validator that validates permission string whether it is a valid permission. Stores any “seen” permissions in its instance as cache to reduce db queries.
- megforms.utils.get_permission_by_name(permission: str) Permission
Gets permission object based on permission string.
- Parameters:
permission – Permission string, for example ‘calendar.add_auditschedule’
- Returns:
Permission object
- Raises:
Permission.DoesNotExistif permission cannot be found
- megforms.utils.permission_to_str(permission: Permission) str
Given permission object, returns string representing the permission in str format. inverse of get_permission_by_name. Note that this function needs to query the related content_type object.
- megforms.utils.filter_auditors_by_permission(auditors: QuerySet[Auditor], permission: Permission | str, *objs: AuditForm, include_global=True) QuerySet[Auditor]
Filters given user queryset by permission and returns only users who have the given permission. Includes superusers who have all permissions implicitly as well as users who have the permission via group membership.
Warning
This utility does not filter by form. If user has the permission globally, but not access to the form, they will still be included in the result.
Do not rely on this function to filter users by form. Use
for_audit_form()instead.- Parameters:
auditors – the queryset to filter on
permission – perm obj or its codename, for example
qip.change_issueobjs – optional list of forms. If passed, users who have the permission only for those forms will be included in the result query. If blank, only global permissions are taken into account
- Include include_global:
whether to include global auditors and superusers
- Returns:
queryset of users where each one has the requested permission globally or in any of the passed
objs
- megforms.utils.auditors_with_perm(permission: Permission | str, *objs: AuditForm, include_global=True) Q
A Q object for
Auditorqueryset that filters objects based on permission.- Parameters:
permission – perm obj or its codename, for example
qip.change_issueobjs – optional list of forms. If passed, users who have the permission only for those forms will be included in the result query. If blank, only global permissions are taken into account
- Include include_global:
whether to include global auditors and superusers
- Returns:
Qobject applicable to Auditor queryset that results in filtering by permission
- megforms.utils.get_permissions(permissions: Collection[str] = ('megforms.view_auditor', 'megforms.add_auditor', 'megforms.change_auditor', 'megforms.delete_auditor', 'megforms.view_team', 'megforms.view_auditform', 'megforms.delete_auditsession', 'megforms.view_department', 'megforms.add_ward', 'megforms.view_ward', 'megforms.change_ward', 'megforms.delete_ward', 'megforms.add_room', 'megforms.change_room', 'megforms.delete_room', 'calendar.view_auditschedule', 'calendar.add_auditschedule', 'calendar.change_auditschedule', 'calendar.delete_auditschedule', 'dashboard_widgets.view_dashboard', 'dashboard_widgets.view_publicdashboardviewconfig', 'information.view_information', 'qip.view_commonissuechoice', 'qip.export_data', 'emails.view_reportrule', 'calendar.view_timelineevent', 'audit_builder.export_data'), validate=False) QuerySet
Gets permission objects based on permission strings. Optionally validates that number of permissions found matches input permissions
- Parameters:
permissions – Permission strings, for example [‘calendar.add_auditschedule’]
validate – whether to validate that all permissions in input string exist (results in additional SQL query)
- Raises:
ValueError – if
validateis set toTrueand not all permissions are valid permission strings- Returns:
Permission QuerySet
- megforms.utils.calculate_image_size(size: tuple[int, int], new_height: int) tuple[int, int]
Calculates new image size to match expected height
- Parameters:
size – size tuple (width, height)
height – new height
- Returns:
size tuple (width, height)
- megforms.utils.get_bmp_thumbnail_path(image_path: str, size_h=150) str
Gives path to bmp file that works with Excel, and create one if needed
- Parameters:
image_path –
size_h – expected width of new bitmap
- Returns:
path to proper bmp thumbnail ready to put in excel sheet
- megforms.utils.strict_password_enforced(user: User) bool
Checks whether user account should be covered by strict password checks due to patient data access
- megforms.utils.daterange_to_months(from_date: date, to_date: date) str
Converts range of dates and returns month or multiple months containing selected dates
>>> utils.daterange_to_months( timezone.datetime(2018, 7, 1), timezone.datetime(2018, 7, 12)) 'July 2018' >>> utils.daterange_to_months( timezone.datetime(2017, 12, 1), timezone.datetime(2018, 1, 12)) 'December 2017 - January 2018' >>> utils.daterange_to_months( timezone.datetime(2018, 7, 1), timezone.datetime(2018, 8, 12)) 'July - August 2018'
- megforms.utils.get_compliance_level(compliance_levels: tuple, compliance: float | None) int | None
Gets compliance level bucket based on compliance and min compliance level
- Parameters:
compliance_levels – tuple (first, second) containing compliance levels
compliance – compliance percentage (float) between 0 and 1 or None
- Returns:
Compliance level: 0 (lowest) to 2 (highest)
- megforms.utils.get_compliance_level_name(compliance_levels: tuple, compliance: float | None, names: Sequence[T] = ('bad', 'fine', 'excellent'), default: T = '') T
Picks the name of compliance level based on compliance and tuple of levels
- Parameters:
compliance_levels – tuple (first, second) containing compliance levels
compliance – compliance percentage (float) between 0 and 1, or None
names – tuple of custom names for compliance (ordered from worst to best)
default – the default value, used if compliance is unknown (None)
- Returns:
string name of compliance level or empty string if cannot be determined
- megforms.utils.get_compliance_level_value(compliance_levels: tuple, compliance: float | None) float
Gets the compliance level value for the provided compliance.
- Parameters:
compliance_levels – tuple (float) containing compliance levels
compliance – compliance percentage (float) between 0 and 1 or None
- Returns:
Compliance level value: (float) a value from compliance_levels
- megforms.utils.clean_sheet_name(name: str) str
Strip out unsupported characters from the spreadsheet name and truncate to supported length
- megforms.utils.get_institution_label_variants_key(institution: Institution | None) str
Return a key for constants.INSTITUTION_LABEL_VARIANTS. Default to ‘institution’. If an Institution is present, return that Institution’s institution_label choice.
- megforms.utils.get_institution_label(institution: Institution | None, capitalize: bool = False, plural: bool = False, lazy: bool = True, plural_all: bool = False) str
Gets a string representing how given institution group names their institutions. By default, it can be “institution” but other institution groups may have other preferences.
- Parameters:
institution – The institution.
capitalize – Whether to capitalize the label or not.
plural – Whether to pluralize the label or not.
lazy – Whether to return a lazy string or not.
plural_all – Whether to return a plural variant that includes the word “all”.
- megforms.utils.get_department_label(institution: Institution | None, capitalize=False, plural=False, lazy=True) str
Gets a string representing how given institution names their departments. By default it can be “department” but other institutions may have other preferences.
- megforms.utils.get_ward_label(institution: Institution | None, capitalize=False, plural=False, lazy=True) str
Gets a string representing how given institution names their wards. By default it can be “ward” but other institutions may have other preferences.
- megforms.utils.get_user_label(institution: Institution | None, capitalize=False, plural=False, lazy=True) str
Gets a string representing how given institution names their users. By default, it can be “user” but other institutions may have other preferences.
- Parameters:
institution – The institution.
capitalize – Whether to capitalize the label or not.
plural – Whether to pluralize the label or not.
lazy – Whether to return a lazy string or not.
- Returns:
user object label string
- megforms.utils.get_issue_label(audit_form: 'AuditForm' | None, capitalize=False, plural=False, lazy=True) str
Gets a string representing how given institution names their QIP issues. By default it can be “issue” but other institutions may have other preferences.
- megforms.utils.get_sub_issue_label(audit_form: 'AuditForm' | None, capitalize=False, plural=False, lazy=True) str
Gets a string representing how given institution names their QIP sub-issues. By default it can be “issue” but other institutions may have other preferences.
- megforms.utils.get_audit_subforms(audit_form: AuditForm, subform_names: Sequence[str] = ()) Iterator[Subform]
Gets all subforms for given audit (hardcoded and custom), limited by subform name
- Parameters:
audit_form – AuditForm instance
subform_names –
only subforms with those names will be returned:
For hardcoded subforms, model_name is used
For custom subforms, name is used for filtering
- Returns:
iterator of subforms (mix of model class and instances)
- class megforms.utils.LocaleWrapper(language: str | User | None)
Context manager to temporarily activate another language. After exiting the context manager will restore previously set locale.
>>> with LocaleWrapper('es'): >>> return gettext("Text that will be translated to Spanish") "Texto que será traducido al español"
- Parameters:
language – language code. Alternatively user instance can be passed. The context manager will determine language using
accounts.utils.get_user_language().
- class megforms.utils.TimezoneWrapper(tz: str | tzinfo)
Context manager to temporarily activate another timezone and return to the previous timezone after exiting
- megforms.utils.iter_translations(lazy_string, skip_untranslated=False, languages=('en', 'pl', 'ro', 'de', 'fr', 'es', 'ar', 'it', 'pt-br', 'zh-hans')) tuple[str, str]
Iterates over translations of given string
- Parameters:
lazy_string – string to be translated (must be a lazy string, for example from gettext_lazy())
skip_untranslated – whether languages that don’t have a unique translation for this string should be skipped
languages – list of language codes to get translations for
- Yield:
Tuple of language code and translation in that language
- megforms.utils.get_domain_url(request: WSGIRequest = None) str
Gets current domain url with the correct http prefix. For example:
- megforms.utils.get_redirect_back_url(request: HttpRequest) str
Gets referrer url, or navigates to landing page if referrer header is not present.
Use if you need a url to navigate the user away from current page
- megforms.utils.make_absolute_url(url: str, request: HttpRequest = None) str
Prepends domain name to url to make the address absolute. Returns url as-is if already prepended
>>> make_absolute_url('/login') "http://localhost:8000/login"
- megforms.utils.get_object_url(obj: Model) str
Gets url for given model instance. The model should implement get_absolute_url method
- Parameters:
obj – a model instance
- Returns:
url to the object as defined in its get_absolute_url, or empty string
- megforms.utils.read_as_base64(file: IO) str
Returns file contents str encoded in base64
- Parameters:
file – opened file object or other stream
- Returns:
file contents as base64 str
- megforms.utils.noop(*args, **kwargs)
A no-op function that does nothing
- megforms.utils.empty_generator() Iterable[None]
A generator that yields no items
- megforms.utils.format_datetime(value: datetime | date | time, force_date: bool = False) str
Formats given date/time int a human-friendly format ready to display to the user
- Parameters:
value – Value to be formatted
force_date – Whether to force the string to always show date only format
- Returns:
- class megforms.utils.DateTimeRange(start: datetime.datetime, end: datetime.datetime)
Represents a datetime range with start and an end datetime
Create new instance of DateTimeRange(start, end)
- start: datetime
start point of the range
- end: datetime
end point of the range
- static from_queryset(qs: QuerySet, field_name: str = 'created') DateTimeRange | None
Gets datetime range from a model field based on a range in a given queryset
- Parameters:
qs – a queryset
field_name – the datetime field in the model
- Returns:
range of datetimes in the given field in the queryset
- static from_observations(*observations: ObservationQueryset, date_getter: Callable[[Observation], datetime.datetime] | None = None, sessions: QuerySet[AuditSession] | None = None) DateTimeRange | None
Creates date range from QuerySets of observations based on their submission date (session end date, or value returned from
date_getter)- Parameters:
observations – querysets of observations
date_getter – optional function that takes observation and returns its date. If not provided, session end date is used
sessions – optional session queryset to used. Filter down the queryset to optimize the query (for example filter by form ids). Unpublished sessions should already be excluded from the qs if necessary, as this utility does not apply further filters to the sessions queryset apart from filtering by observations. If this parameter is not present, all published sessions are used, which can be inefficient (Task #31445)
- Returns:
The range starting at the earliest observation time, to the latest observation. Null if there are no observations
- get_dates() tuple[datetime.date, datetime.date]
Returns only date range for this range object. Strips out time and timezone information from the datetimes
- class megforms.utils.ColorRGB(r, g, b)
Represents color consisting of Red, Green and Blue components where each component is an integer in range 0-255
Create new instance of ColorRGB(r, g, b)
- megforms.utils.hex_to_rgb(hex: str) ColorRGB
Parses colour from hex format (#FFFFFF) and returns it as a color tuple containing R, G, B components (int)
- Parameters:
hex – hexadecimal representation of the colour, optionally starting with a #
- Returns:
color named tuple containing each RGB component as int
- class megforms.utils.ModelBackendHelper
Overrides django Model authentication backend by adding support for:
permission validation
case-insensitive login
- megforms.utils.get_user_landing_page(user: User, next_url=None) str
Returns url for user’s default landing page to redirect after login
- class megforms.utils.DisableReadOnlyValidationFormMixin
A form mixin class that disables validation for disabled fields
- megforms.utils.is_allowed_password_change(user: User) bool
Tells whether given user is allowed to change their account password. Some shared account cannot change their password.
- megforms.utils.fields_in_fieldsets(fieldsets: tuple) Iterator[str]
Returns fields used in django admin fieldsets. For use in tests to show missing or addition fields
excluded - some fields inherited from the base class are excluded from fieldsets but need to be part of the count
- megforms.utils.fields_in_specific_model(model, ignored: set[str] = frozenset({})) list[str]
Returns fields from a model in list format.
excluded - fields inherited from BaseModel are excluded, alongside the id
- megforms.utils.missing_fields_from_fieldsets(fields_from_fieldset: set[str], fields_from_model: list[str]) set[str]
Returns fields that are missing from fieldsets in the AdminModel but present on the Model.
- megforms.utils.validate_recaptcha_token(token: str | empty, platform_key: str, ip: str) bool
Validates if user has passed recaptcha. platform_key is different for each platform: web, android and ios.
- megforms.utils.make_cache_key(*args, **kwargs) str
Given arbitrary values, generates cache key suitable for redis and django memory cache NOTE: Not suitable for memcached because key length and character set is not enforced. If you need to use cached_function with memcached, use a different key function that accounts for these limitations.
- megforms.utils.cached_function(timeout=300, cache_name='default', key_fn=<function make_cache_key>)
Decorator - makes function cache-able using django cache mechanism. Notes:
Return type of the function must be serializable/immutable object
when applying to a class method, you must implement a custom key function (
key_fn) and account for self if cached method uses any class member variables
- megforms.utils.get_compliance_range_by_name(compliance_rate: Literal['incompliant', 'incompliant_and_all_comments', 'pass', 'compliant'], form: AuditForm) tuple[float, float]
Given compliance range name (incompliant, incompliant_and_all_comments, pass, compliant) returns a range (tuple) representing its lowest compliance (inclusive) and highest compliance (exclusive).
- Raises:
ValueErrorif compliance rate is not one of the valid values.
- megforms.utils.field_with_translations(field_name: str, include_default=True) Iterator[str]
Given a translatable field name (using django-modeltranslations lib) returns all language variants of the field name, including the default
- megforms.utils.localize_time_range(start: date, end: date) DateTimeRange
Converts dates to a datetime tuple and adds current timezone information
- megforms.utils.filter_choices(choices: Sequence[Tuple[str, str] | tuple[str, Sequence[Tuple[str, str]]]], fn: Callable[[Tuple[str, str]], bool]) Sequence[Tuple[str, str] | tuple[str, Sequence[Tuple[str, str]]]]
Given a (optionally nested) list of choices, filters the choices and returns a new set of choices filtered in the same shape, but with some items removed. Any empty groups are also removed from the list.
- megforms.utils.filter_choice_values(choices: Sequence[Tuple[str, str] | tuple[str, Sequence[Tuple[str, str]]]], values: Iterable[str]) Sequence[Tuple[str, str] | tuple[str, Sequence[Tuple[str, str]]]]
Given choices and a list of accepted values, filters out any choices whose value is not one of the given values. The input choice list can be either a flat or nested list of choices.
- megforms.utils.validate_and_sort_multi_file_upload(field: Field, files: Sequence[UploadedFile], sorting: bool = True) list[django.core.files.uploadedfile.UploadedFile]
Validates files in a multi-file field individually
- Raises:
ValidationError – if any of the files does not pass validation
- megforms.utils.get_subform_name(subform: Subform | SubObservation) str
Gets name for a hardcoded or custom subform.
- Note:
not to be confused with “display name”. This name is not meant to be displayed to the user.
- Parameters:
subform – A subform or subobservation instance
- Returns:
name of the subform
If subform is a hardcoded model, returns a model name (lowercase). For custom subforms, returns subform’s name.
- Raises:
TypeError – if given object is not a subform
- megforms.utils.get_subform_path(subform: Subform) str
Similar to
get_subform_name(), but for hardcoded subforms, returns dotted class path instead of model name
- megforms.utils.get_set_value_placeholders_data(auditor: Auditor | None = None, ward: Ward | None = None) dict
Get placeholder dict for “set_value” in custom field conditional logic
- Parameters:
auditor – Current Auditor
ward – Current Ward
- Returns:
dict with placeholder keys and values
- megforms.utils.get_set_value_placeholders_dependant_data(wards: Iterable[Ward]) dict
Get placeholder mapping dict for “set_value” dependant fields in custom field conditional logic
- Parameters:
wards – Ward options
- Returns:
dict with placeholder keys and values
- megforms.utils.get_condition_set_value(session: AuditSession, field: CustomField, condition: Condition) SerializedAnswer
Returns “set_value” value for a condition and calculates any placeholders that are used for it
- Parameters:
session – Related AuditSession
field – CustomField
condition – condition record
- Returns:
Returns answer value after being calculated by placeholder
- megforms.utils.check_edit_access(audit_form: AuditForm | None, auditor: Auditor | None) bool | None
Checks if the user has the required level to edit the form. This function does not check user permission or whether user has access to the form.
- Parameters:
audit_form – The form to be audited.
auditor – The user performing the audit.
- Returns:
True if the user has the required level to access the form, False if the user does not have the required level, None if either audit_form or auditor is None.
- megforms.utils.is_one_subform_selected(filter_form: OverviewFilterForm | None) bool
Tells whether exactly one subform is selected in filter form. Returns True only if the form has subforms and user has selected exactly one. Returns False in every other scenario, including if filter form is null
- megforms.utils.expand_aggregate_compliances(data: Iterable[dict]) Iterator[tuple, tuple[Compliance, int]]
Parses data generated using
aggregate_by_answer():If there are any multichoice fields, the object is returned multiple times, once for each compination of answers
- Parameters:
data – a sequence of dictionaries containing ‘count’, ‘compliance’ and other fields
- Returns:
a list containing tuple with answers, and compliance/count tuple
- megforms.utils.combine_aggregates_values(data: Iterable[tuple, tuple[Compliance, int]]) dict[tuple, tuple[Compliance, int]]
From a stream of repeatable values assigned to compliance data (compliance, weight), builds a dictionary that maps unique data set to an average of its compliance
- Parameters:
data – input data, an iterable where each item is a tuple containing a set of values, and its compliance/weight tuple
- Returns:
dictionary aggregating unique values
- megforms.utils.extract_url_id(request: HttpRequest, arg_name='object_id') int | None
Extracts object’s id encoded into request url.
The function may raise an exception if value found at url does not parse into an integer.
- Parameters:
request – the incoming http request containing the object id in its request
arg_name – name of the url argument containing the id. The default value is suitable to use with django admin.
- Returns:
id extracted from the url, or none if url does not contain it.
- megforms.utils.is_user_allowed_impersonate(user: User, user_cleaned_data: dict | None = None, auditor_cleaned_data: dict | None = None) bool
Checks if the user is allowed to impersonate as per Task #29870. User must be a (global) staff member, or superuser.
- Used in multiple contexts:
Django impersonation middleware via check_allow_impersonate()
Form validation in CustomUserChangeForm and AuditorAdminForm, hence optional clean data
Auditor model validation in clean() and save(), hence optional auditor
CustomUserAdmin to show / start impersonation
- User attributes (is_superuser & is_staff):
Checks first user_cleaned_data for latest changes to user
If no changes checks user directly
- Auditor data access (level):
Checks cleaned data for latest level
Otherwise checks user.auditor.level
Otherwise defaults to LEVEL_INSTITUTION
- Parameters:
user – The user requesting impersonation
user_cleaned_data – An optional dict of new User values (from a form)
auditor_cleaned_data – An optional dict of new Auditor values (from a form)
- Returns:
True if the user is allowed to impersonate, False otherwise
- megforms.utils.level_to_int(level: str) int
given a user level, returns its integer representation so that levels can be compared
- Parameters:
level – one of the valid level choices
- Returns:
an integer representing the place of the level in hierarchy, starting at 0
- Raises:
ValueError – if the input level is not valid
- class megforms.utils.LazyTemplateValue(func: Callable, *args, **kwargs)
Allow any value or func to be lazily loaded inside template only when referenced in the template.
Lazy values can be passed with and without args and kwargs in context of a rendered templatetemplate_html = ''' <ul> {% if obj_index %} <li>{{obj_index}}</li> {% endif %} {% for obj in list %} <li>{{ obj }}</li> {% endfor %} </ul> ''' def get_by_slug(code: str): return qs.filter(code=code).first() def get_list(): return list(qs.order_by('id')) return render( request, template_name='template.html', context=dict( obj_index=LazyTemplateValue(get_by_slug, 'index') list=LazyTemplateValue(get_list) ), )
- Parameters:
func – Function to evaluate when using the value
args – function args
kwargs – function kwargs
- property value: Any
evaluate and return lazy value
- megforms.utils.get_object_creator(obj: Any) User | None
Get the user that has originally created provided object if exists
- Parameters:
obj – object to be inspected
- Returns:
User that has created the object
- megforms.utils.safe_linebreaks(text: SafeString | str) SafeString
Add lines breaks on html string then convert to SafeString, however if string is already a SafeString, only add lines break and keep html as is
- Parameters:
text – SafeString or str of html
- Returns:
- megforms.utils.delete_issue_files(issue_qs: QuerySet) None
Deletes all files associated with issues in the given queryset. Queries for all IssuePhoto, IssueDocument, and IssueAudio records associated with the issues and deletes their physical files.
- Parameters:
issue_qs – QuerySet of Issue objects
- megforms.utils.get_observation_ward_ringfencing_q(auditor: Auditor, *forms: AuditForm, readonly=True) Q
Auditor ring-fence visible observations by ward for forms. Show observations where auditor is selected from a dropdown.
This method should not be used outside of implementations of for_auditor_forms. If you need to implement observation ringfencing, use for_auditor_form.
- Parameters:
forms – list of forms for the purpose of building the query
readonly – whether the resulting query should show only items for read-only access. This can result in showing more items if user has readonly access to more observations due to ring-fencing settings.
- Returns:
A Q object that can be used in an observation queryset to filter by wards
- megforms.utils.update_usernames_to_saml_name_id(users_queryset: QuerySet, admin_user: User | None = None) Dict[str, List[str]]
Updates usernames to match their SAML name_id for users with SAML identities
- megforms.utils.compute_multi_ward_map(observations: Iterable['ObservationQuerySet']) set[int]
Compute the set of session IDs whose observations span multiple wards.
For each provided ObservationQuerySet, we aggregate observations by
session_idand count distinct wards. Any session that has more than one distinct ward is included in the resulting set.Notes: - Accepts any iterable of ObservationQuerySets (generator, list, tuple, etc.). - Each queryset may refer to a different observation model; aggregation is performed per-QS.
- Parameters:
observations – Iterable of ObservationQuerySets to inspect.
- Returns:
Set of session IDs with more than one distinct ward among their observations.
- megforms.utils.change_ward(user: User, observation: CustomObservation, target_ward: Ward, update_issues: bool, institution: Institution) tuple[bool, Ward, Ward]
Changes the ward associated with an observation. Creates a changelog and handles edge cases like updating issues and the session’s ward. The session ward will only be updated if the observation is the only observation in the session.
- Parameters:
user – The user making the change.
observation – The custom observation to be updated.
target_ward – The ward to be added to the observation.
update_issues – Whether to update issues or not.
institution – The institution for generating the ward label in the log entry change message.
- Returns:
A tuple with a boolean value representing if the ward was changed or not, the target ward and the old ward.
- class utils.htmx.HTMXUtilsViewMixin
Provides view with utilities for handling htmx requests:
- htmx:
provides a HTMX object for current request
- htmx_redirect:
creates a HTTP response that triggers HTMX to redirect user to a url
- htmx_redirect(url: str | None = None, *, preserve_arguments=True) HttpResponse
Http response triggering htmx to refresh the page or redirect user to given url
- Parameters:
url – url to redirect to, or null to redirect back to the same page
preserve_arguments – when redirecting to the same page, set this to
Falseto clear any GET parameters
- class utils.htmx.HTMXViewMixin
Mixin for views that redirects HTMX requests to a separate method
Important
if you’re using this mixin and
PermissionRequiredMixin, in order to enforce the permission, this mixin should be listed after. For example:class ViewWithHtmxMixin(PermissionRequiredMixin, HTMXViewMixin, View): permission_required = 'megforms.view_institution'
- handle_htmx(request: HttpRequest, htmx: HtmxDetails, *args, **kwargs) HttpResponse
Handle HTMX request and serve response
- render_htmx_response(template_name: str, include_base_context=True, extra_context: dict | None = None) TemplateResponse
Helper method to render template into htmx response and reuse the same kwargs as the view itself
- Parameters:
include_base_context – whether to pass view’s context into the template
extra_context – context to pass to the template in addition to any view context
- Returns:
Response containing rendered template
- utils.html.strip_html_tags(html: str) str
Strings html tags from input HTML text
- Returns:
plain text from input HTML text
- utils.html.append_class(classes: str, new_class: str) str
Adds a class to HTML
classproperty. Ensures space separation between classes.- Parameters:
classes – string containing existing classes
new_class – class name to add
- Returns:
updated classes string
- utils.diff.generate_diff(content1: str, content2: str) Iterable[tuple[int, str]]
Generates the difference between two strings using
diff_match_patch. The output can be passed todiff_to_html()to present it on a html page.- Parameters:
content1 – First string to compare
content2 – Second string to compare
- Returns:
Difference between the two strings in form of an iterable (list) of tuples. Each tuple contains a difference information: 0 if unchanged, -1 or 1 to indicate whether the text was added or removed against content1.
Example: >>> generate_diff(“hello world”, “hello world”) [(0, ‘hello world’)] >>> generate_diff(“hello world”, “hello WORLD”) [ (0, ‘hello ‘), (-1, ‘world’), (1, ‘WORLD’) ]
- utils.diff.diff_to_html(diffs: Iterable[tuple[int, str]]) Iterable[str]
Turns diff generated from
generate_diff()into a safe HTML string by replacing newlines with HTML br tags,, and individual differences into appropriate <ins/del/span> tags depending on the diff value.- Parameters:
diffs – Differences iterable
- Yields:
parts of the html string, each containing a separate piece of the diff
- utils.pandas.extract_json_data(dataframe: DataFrame, column: str, json_columns: tuple[str] | None = None) DataFrame
Creates a dataframe representing columns from a json column in another dataframe. The resulting dataframe retains index from the source dataframe.
- Parameters:
dataframe – source dataframe
column – name of the column containing json dicts
json_columns – names of attributes in the json dicts to be used
- Returns:
a new dataframe containing data from
json_columns, usingdataframeindex
- utils.pandas.expand_json_field(dataframe: DataFrame, json_field: str, *, json_columns: tuple[str] | None = None, rsuffix: str = '') DataFrame
Takes a column containing json fields and expands it into a dataframe. Resulting dataframe is joined back into the dataframe
- Parameters:
dataframe – the df containing data and json field
json_field – name of the column corresponding to the json field
json_columns – keys from the json data dictionary to be extracted
rsuffix – Suffix for overlapping custom answers column names
- utils.pandas.weighted_mean(df: DataFrame, weight_column: str, group_by: str = None) Callable[[Series], float]
Builds a callable that can be used by pandas to calculate weighted mean. Nulls and NaNs are excluded before calculating mean. Empty and null averages are represented as NaN.
Example:
df = df.agg({ 'compliance': weighted_mean(df, 'compliance_weight'), })
Example:
df = df.groupby('ward').agg({ 'compliance': weighted_mean(df, 'compliance_weight', 'ward'), })
- Parameters:
df – source dataframe the operation will be performed on
weight_column – name of the df column containing weight
group_by – column for grouping, if applied
- Returns:
callable that can be passed to
agg()
- utils.misc.get_metadata() dict
Get metadata about current cms instance
- class utils.collections.parameteriseddefaultdict(factory: Callable[[_KT], _VT], *args, **kwargs)
like defaultdict, but take a default_factory function that runs on the missing key.
>>> squares = parameteriseddefaultdict(lambda x: x**2) >>> squares parameteriseddefaultdict(<function __main__.<lambda>(x)>, {}) >>> squares[2] 4 >>>squares parameteriseddefaultdict(<function __main__.<lambda>(x)>, {2: 4})
- exception utils.timeout.TimeoutError(value='Timed Out')
- utils.timeout.timeout(seconds: int)
Function/method decorator that will raise TimeoutError if calling it exceeds the given execution threshold
- class utils.filter_form_mixin.BaseFilterFormMixin
Base class for Mixins that provides filtering forms. Support caching Usage requires to set up filter_form_class variable to FilterForm class
- get_filter_form_kwargs(**kwargs) dict
updates kwargs with required data
- get_filter_form() Form
Returns object of initialized Form
- property filter_form: Form
initialize filter_form_class with kwargs
- get_queryset() BaseQuerySet
Provides base queryset for required model
- filter_objects(qs: BaseQuerySet) BaseQuerySet
Applies filtering selected by the user to the queryset
- get_context_data(**kwargs) dict
updates context for template with form and cache_token for usage inside htmx
- save_filter_state(request: HttpRequest) str
Stores filter state in the session and returns session key
- get_filter_redirect_url(request: HttpRequest, filter_state_key: str) str
Builds url to reload this page using new filter state
- utils.field_answers.get_display_value(observation: BaseAuditModel, field: Field) str
Extracts value from observations field as display string
- Parameters:
observation – observation to fetch value from
field – field that data we want to extract
- Returns:
string value of observations field value
- utils.field_answers.get_custom_value(observation: CustomObservation, custom_field: CustomField) str
Extracts value from observations field as display string. for CustomFields
- Parameters:
observation – observation to fetch value from
custom_field – field that data we want to extract
- Returns:
string value of observations field value
- class utils.pdf.WeasyPrintFileResultDict
- utils.pdf.custom_url_fetcher(url: str, *args, **kwargs) WeasyPrintFileResultDict
Custom URL fetcher for WeasyPrint that handles image paths consistently across Docker and local environments.
For relative media URLs, this fetcher handles both path conversion and file reading to avoid errors with WeasyPrint’s django_url_fetcher path parsing.
- Parameters:
url – The URL to fetch, typically beginning with ‘file://’ followed by a media URL
args – Additional positional arguments passed to django_url_fetcher
kwargs – Additional keyword arguments passed to django_url_fetcher
- Returns:
Dictionary containing file data in WeasyPrint’s format (string, mime_type, encoding, filename, redirected_url, file_obj)
- Path handling rules:
Detects relative media URLs that WeasyPrint converts to file:// format
Converts these to absolute filesystem paths using MEDIA_ROOT
Reads file data directly instead of relying on URL fetcher path parsing
Example:
Original relative - ‘/media/images/photo.jpg’ WeasyPrint conversion - ‘file:///media/images/photo.jpg’ Fetcher conversion (Windows) - ‘C:/Users/user/Documents/meg-qms/media/images/photo.jpg’ Fetcher conversion (Linux) - ‘/opt/meg-qms/media/images/photo.jpg’
- utils.pdf.html_to_pdf(html: str, output_file: Path | IO, *, stylesheet: str | Iterable[str] = ())
Converts HTML string to a PDF file, works with custom fonts implemented in ‘megdocs/static/css/fonts.css’
- Parameters:
html – HTML string
output_file – Path to PDF file, or a file-like object
stylesheet – absolute path to css file(s) used to style the output PDF
Example usage using a temporary file:
with tempfile.NamedTemporaryFile(prefix='html_conversion', suffix='.pdf') as pdf_file: html_to_pdf(draft.content, pdf_file, os.path.join(settings.ROOT_DIR, 'meg_forms/megdocs/static/css/document.css')) # Save pdf to django model object = ExampleModel() object.pdf_document.save("filename.pdf", pdf_file)
- utils.pdf.pdf_to_txt(pdf: Path, **kwargs) str
Extracts text from a PDF file Uses PDF Miner library to extract text from a PDF file.
- Parameters:
pdf – path to the PDF file
kwargs – any additional kwargs for pdfminer
- megforms.timezone_utils.get_database_tznames() frozenset[str]
Queries database for all supported timezone names
Unpublishing & restoring
- utils.unpublish.get_child_models(parent: type[megforms.base_model.BaseModel]) Iterator[tuple[type[megforms.base_model.BaseModel], str]]
looks for models that have a fk relation to given
parentmodel and yields them- Parameters:
parent – parent model class
- Yield:
tuples containing model class, field name of the fk with relation to the parent, and the on_delete value of the foreign key
- class utils.unpublish.Unpublisher(objs: QuerySet | BaseModel, user: User | None = None, force: bool = False)
Collects objects for un-publishing by visiting relations and storing object ids. The collected objects can be unpublished in bulk by invoking its
unpublish()method.Unpublishing objects results in creation of a
UnpublishLogEntryobject that serves as a log of objects affected by the action and can be used tore_publish()the affected objecs.Note
this class operates exclusively on models that extend
BaseModel. It is not suitable for django built-in models such asUser,Groupor models from third-party libraries as they do not have apublishfieldOptionally user object can be designated for logging purposes.
Important
this class does not verify that user has the permission to unpublish objects, or that the user even has access to those objects or its children.
Example usage:
unpublisher = Unpublisher(Room.objects.filter(name='Test Room')) if unpublisher: print(unpublisher.as_strings()) unpublisher.unpublish()
- Parameters:
objs – objects to be unpublished. Can be single instance, or a queryset.
user – optional user if action is being performed by a user rather than automated job.
force – whether to include already unpublished objects when collecting
- objs: dict[type[megforms.base_model.BaseModel], set[int]]
maps model to a set of ids of objects to be unpublished
- user: User | None
user performing the action
- force: bool
whether to include already unpublished objects when collecting
- as_strings() Iterable[tuple[str, Iterable[str]]]
Represents collected objects as a structure with model name (plural) to object names. Note that model names are localized into a non-lazy string. For example:
[ ("Wards", ["Ward 1", "Ward 2"]), ("Rooms", ["Room 101", "Room 102", "Room 201"]), ]
- collect_child_objects(parent_model: type[megforms.base_model.BaseModel], parent_ids: Iterable[int])
Collects child objects for parent objects in given model
- collect_generic_relations(parent_model: type[megforms.base_model.BaseModel], parent_ids: Iterable[int])
Collects objects with a GenericRelation to given model instances.
The implementation hardcodes some rules as to which models will have a generic relation and may need to be updated if comments or QIP functionality is expanded to other models.
- Parameters:
parent_model – parent model class
parent_ids – ids of parent objects being unpublished
- collect_objs(objs: QuerySet)
Adds provided objects and their dependants to the collection.
Only published objects are
- unpublish(*, log_action=False, log_comment: str | None = None) int
Unpublish the collected items atomically, and logs an unpublish entry by creating a
UnpublishLogEntry.Optionally can also log user’s action to
LogEntry.- Parameters:
log_action – Whether action should be logged to
LogEntry(requires thatuseris set)log_comment – optional comment to be added to log entry if
log_action is set
- Returns:
total number of unpublished objects
- unpublish_logged(comment: str | None = None) int
Un-publishes the object(s) and logs the action with a comment to Django’s
LogEntrywithaction_flag=CHANGE.- Parameters:
comment – comment to be added to the action log
- Returns:
number of affected objects
- utils.unpublish.unpublish(user: User | None, objs: BaseQuerySet | BaseModel, log_comment: str | None = None) int
Un-publishes object and cascades changes to its child objects.
- Parameters:
user – Usert whose to whom the action is attributed (logged). If passed, the action will be logged and reversible.
objs – object(s) to be unpublished. Can be already un-published
log_comment – comment to be added to log entry - requires that user is also passed
- Returns:
Total number of updated objects, including child objects. Object already unpublished are included in this count.
Logging & Metrics
- logger.get_logger() Logger
Gets singleton
loggerinstance
- logger.log_debug(message: str, *args)
Log a debug message
- logger.get_tracer() Tracer
Gets OpenTelemetry
Tracerinstance
- logger.trace_span(name: str, span_attrs: Mapping[str, str | bool | int | float | Sequence[str] | Sequence[bool] | Sequence[int] | Sequence[float]] | None = None) Span
Helper context manager to trace code within the span
- Parameters:
name – trace span name
span_attrs – any custom values to be included in trace. A Dictionary mapping string key to a value (
str,int,bool,float, aSequenceorNone)
Example:
>>> with trace_span('some operation') as span: >>> do_something()
- logger.trace_decorator(name: str | None = None, *, trace_args: bool = False)
Decorator used to trace entire function
- Parameters:
name – Optional trace span name. Defaults to function path.
trace_args – Wheter args and kwargs from the function calls should be included in trace WARNING: do not trace args in functions that can accept sensitive information or are expensive to compute
Usage:
>>> @trace_decorator(name='do something') >>> def some_function(): >>> do_something()
Can also be used without arguments
>>> @trace_decorator >>> def some_function(): >>> do_something()
- class logger.catch_exception(*exceptions: type[Exception])
Catches exception within the context and reports them to Sentry and logger Similar to
contextlib.suppress, but instead of completely ignoring the exception, it reports the exception to Sentry.Use only if the piece of code within the context is allowed to fail without side effects, but you still want to track the exceptions.
Examples:
with catch_exception(): ... with catch_exception(TypeError, KeyError): ...
Equivalent to:
try: ... except Exception: sentry_sdk.capture_exception(exc_value)
- Parameters:
exceptions – types of exceptions to catch. Leave blank to catch all exceptions
- class regions.metrics.RegionInsightsReporter
Reports server metrics to Azure Insights. All metrics are tagged with current site domain.
- IMPORTANT:
Do not use this class if
INSIGHTS_CONNECTION_STRINGis not set
More information in Task #27621
- log_metric_value(metric: Gauge, value: int | float | Callable[[], int | float], *, sample_rate: float = 0.01)
Report metric value to Azure Insights. Value can be callable, it will be evaluated only if
INSIGHTS_SAMPLE_RATEdetermines that the value should be reported.- Parameters:
metric – the metric being reported
value – the value to be reported, or a callable returning the value (will be evaluated once)
sample_rate – rate at which this metric should be sampled. Default value follows the global setting, but can be overridden to ensure that the metric is reported this time.
- log_celery_queue_size(size: int | Callable[[], int])
Reports current celery task queue size
Celery
- megforms.celery_utils.get_celery_task_path(task: str | Task) str
Given a celery task reference, returns its path as a string. Does nothing if passed task is already a
str.- Parameters:
task – task reference (class, function), or path
- Returns:
task path
- megforms.celery_utils.schedule_task(name: str, start_time: datetime | timedelta, *, task: str | Task, task_args: list | dict | None = None, overwrite=False, **kwargs) PeriodicTask
Schedule a once-off celery task to be executed at specified time by creating a PeriodicTask object.
- Parameters:
name – unique name for this schedule
task – the celery job to trigger at specified time
start_time – time when job should be triggered. Either time interval (from now) or absolute datetime
task_task_args – args or kwargs that will be passed to the task on execution
overwrite – if task with this name already exists, delete it before scheduling new task. If set for False and task already exists the function will raise an error
- Params kwargs:
Anny additional arguments for
PeriodicTaskmodel
- megforms.celery_utils.schedule_repeating_task(name: str, start_time: datetime, interval: timedelta | relativedelta | IntervalSchedule, *, task: str | Task, task_args: list | dict | None = None, **kwargs) tuple[django_celery_beat.models.PeriodicTask, bool]
Schedules a celery task to run under given name. Updates the schedule if name already exists.
- Parameters:
name – Unique name of this schedule
start_time – First time when the schedule should be triggered
interval –
IntervalScheduleortimedeltaorrelativedeltaobject representing the repeat frequency. For once-off schedules, usetimezone.timedelta(0).task – The task to be ran. Pass task’s path (string) or its reference (function or class). The value will be parsed using
get_celery_task_path().task_args – Optional args (list) or kwargs (dict) to be passed into the
taskwhen executing.kwargs – Any additional arguments for
PeriodicTaskconstructor
- Returns:
tuple containing the
PeriodicTaskandboolwhether it was created (True), or updated (False)
Date & Time utils
- megforms.calendar.utils.iter_dates(start: date, end: date | None = None, step: timedelta | relativedelta = datetime.timedelta(days=1))
Iterates date range analogous to Python’s
range()- Parameters:
start – the first date to be yielded
end – the last date to be yielded, or None to iterate indefinitely
step – increment between dates; defaults to 1 day to yield each date, can be changed to skip days
- Yield:
each date from
starttoendinclusive
- megforms.calendar.utils.year_quarters(*years: int) Iterable[Tuple[date, date]]
Generates first and last day of each quarter in the given year(s)
- Parameters:
years – any number of years
- Yield:
four date pair are yielded for each year passed in
years, prepresenting each quarter of each year.
- megforms.calendar.utils.get_quarter(date: datetime | date) tuple[int, Tuple[datetime.date, datetime.date]]
Gets quarter number and its start/end dates for given date or time
- Parameters:
date – the date or datetime. If datetime is passed, it is converted to local time first
- Returns:
tuple containing the quarter number (0-3), and its first and last day
- megforms.calendar.utils.get_year_half(date: datetime | date) date
Returns a date representing the start date of the year half.
- Parameters:
date – the date or datetime. If datetime is passed, it is converted to local time first
- megforms.calendar.utils.get_quarter_string(date: datetime | date) str
Gets string representation of quarter the given date belongs to
- Parameters:
date – date or datetime
- Returns:
Quarter string representation, for example “Q3 2021”
Example:
>>> get_quarter_string(datetime.date(2021, 6, 15)) 'Q2 2021'
- megforms.calendar.utils.get_half_year_string(date: datetime | date) str
Gets string representation of the year half the given date belongs to
- Parameters:
date – date or datetime
- Returns:
Half year string representation, for example “H1 2023”
Example:
>>> get_half_year_string(datetime.date(2023, 1, 1)) 'H1 2023'
- megforms.calendar.utils.get_month_pair(date: datetime | date) date
For month pairs jan/feb, mar/apr, for a given date, returns a date representing the start date of the month pair (first day of first month in the pair with the same year as the original date)
- megforms.calendar.utils.generate_last_four_quarters_ranges(today: date, translatable: bool = False) Iterable[Tuple[str, Tuple[date, date]]]
Generate predefined filters for the last four quarters relative to the given date.
- Parameters:
today – Today’s date
translatable – Whether quarters are translatable or raw
- Returns:
Iterable of (quarter label, tuple of quarter start date, quarter end date) tuples
- megforms.calendar.utils.generate_date_range_choices(today: date, include_future: bool = False) Iterable[Tuple[str, Tuple[date, date]]]
Generates various date ranges for filter dropdown presets
- megforms.calendar.utils.date_range_choices(today: date, include_future=False) Tuple[str, Tuple[str, str]]
Date range choices for given date, cached by date
- Parameters:
today – Today’s date
include_future – Include future dates
- Returns:
- megforms.calendar.utils.number_passed_leap_years_for_range(start_date: date, end_date: date) int
Calculates the number of leap years between two dates with one caveat. The number is the number of years that are leap and Feb 29th is within the range. If the start year is leap, and we passed Feb, then this year won’t be count in the range as leap year. Similarly, if the end year is leap, and Feb 29th is still not hit, then this year won’t be count in the range as leap year.’
- class megforms.calendar.utils.TimelineEventTuple(name: str, date: date, color: str)
A simple tuple version of Timeline event, which allows its usage without object / db creation
Create new instance of TimelineEventTuple(name, date, color)
- name: str
Alias for field number 0
- date: date
Alias for field number 1
- color: str
Alias for field number 2
- megforms.time_tracking_utils.annotate_times(qs: ObservationQueryset, time_object: dict[str, Union[str, int, bool, list, float, NoneType]], annotation_name: str) ObservationQueryset
Annotates queryset with a time value. Uses
LogEntryandTrackerLogEntrytable to extract the time when action was performed.Example:
observations = CustomObservation.objects.all() # Annotate start_time with a value of when the observation was created observations = annotate_times(observations, {"time_created": ""}, 'start_time') # Annotate review_time with the time when int_field was set to 20 for the first time. observations = annotate_times(observations, {"int_field": 20}, 'review_time')
- Parameters:
qs – an observation queryset to be annotated
time_object – a dict representing field or action (key) and optionally a value to filter by time given field was reviewed
annotation_name – name to bind the value to in the queryset
- Returns:
the same queryset, but with added annotation
- megforms.time_tracking_utils.timedelta_to_string(timedelta: timedelta, verbose: bool = False) str
Converts a timedelta object to a human-readable string.
- Parameters:
timedelta – The timedelta object to convert.
- Param:
verbose: when set to false, limits output to two time units, for example “1 year, 2 months”
- Returns:
The human-readable time string.
- megforms.time_tracking_utils.seconds_to_time_string(seconds: int, verbose: bool = False) str
Converts number of seconds to a human-readable string. Negative delta is converted to positive. verbose - when set to false, limits output to two time units, for example “1 year, 2 months”
Examples:
>>> seconds_to_time_string(20000) '5 hours, 33 minutes' >>> seconds_to_time_string(200000, verbose=True) '2 days, 7 hours, 33 minutes' >>> seconds_to_time_string(200000, verbose=False) '2 days, 7 hours'
- megforms.time_tracking_utils.find_start_month(month: int, months: list[int]) int
Get first allowed month for given months
- Parameters:
month – month
months – Allowed month
- Returns:
first allowed month
- megforms.time_tracking_utils.months_between_two_dates(start_date: date, end_date: date, months: list[int] | None = None) Iterator[date]
Get a list of datetime for each month between start and end dates
- Parameters:
start_date – Start Date
end_date – End Date
months – allowed months array (TWO_MONTHS, QUARTER_MONTHS)
- Returns:
list of datetime for months
- megforms.time_tracking_utils.get_first_day_of_quarter(time: datetime) datetime
Helper method to get the first day of any given quarter.
- Parameters:
time – a datetime used to find the quarter.
- Returns:
first day of the quarter.
- megforms.time_tracking_utils.get_datetime_of_last_day_of_last_month() datetime
Shorthand for finding the last day of the previous month
Accounts
- accounts.utils.get_user_institution(user: User) Institution | None
Returns user’s institution. For auditors it simply returns their institution instance. For eGuide users, returns institution of the first eGuide they have access to. Returns None for users who are not auditors and don’t have access to any eGuides.
- accounts.utils.get_user_institutions(user: User) InstitutionQuerySet
Works like
accounts.utils.get_user_institution()but returns multiple institutions for higher-level users like group auditors
- accounts.utils.get_user_language(user: User) str | None
Determines user’s language based on user’s preference. If user does not have a preference, return user institution’s language.
If null is returned, it is recommended to use server’s default language.
- Parameters:
user – the user instance
- Returns:
language code or null or language cannot be determined by user’s preference or institution setting.
- accounts.utils.must_pass_captcha(user: User | RegionUserLink) bool
Determines if the auditor must pass a captcha test on their next login. This is based on the institution’s password policy.
- accounts.utils.get_is_auth_blocked(region_user_link: RegionUserLink, auth_restriction_type: str, request: HttpRequest | None = None) bool
Checks whether account in another region is restricted from logging in to the account. Makes a request to user’s region to make the check
- Parameters:
region_user_link – the user account in another region
auth_restriction_type – type of restriction to check for:
AUTH_RESTRICTION_TYPE_BLOCKEDorAUTH_RESTRICTION_TYPE_CAPTCHArequest – current HTTP request
- Returns:
True if the requested restriction is applied to the account (account blocked or captcha)
- accounts.utils.check_login_blocked(user: User | RegionUserLink, request: HttpRequest | None = None) bool
Determines if a user is blocked from logging in. If there hasn’t been a successful login within the limit, block user login until policy timeout has passed. Blocks the user if they are a global account that shouldn’t be a global account.
- accounts.utils.build_app_deep_link(request: HttpRequest | None, auditor: Auditor | None, institution_id: int | None = None, form_id: int | None = None) str
Builds pre-authenticated deep link to the client web app for the given institution and form id
- accounts.utils.get_unique_username_for_usernames(username: str, usernames: Iterable[str], append: int = 1) str
- Raises:
ValidationError if given username is not a valid username as per django username rules
- accounts.utils.generate_locally_unique_usernames(usernames: dict[str, str]) dict[str, str]
Checks if usernames are unique locally. For any non-unique usernames, ints are appended to achieve uniqueness. Maintains a map between generated username and the original username.
- accounts.utils.is_super_or_global(user: User) bool
Checks if a user has a high level of access to data in MEG.
- accounts.utils.is_super_global_or_staff(user: User) bool
Checks if a user has a high level of access to data in MEG, including staff users.
- accounts.utils.is_super_or_global_allowed(institution: Institution, email: str) bool
Checks if a user with the provided properties is allowed to be a super or global user.
- accounts.utils.is_two_factor_user(user: User) bool
Determines if the user has two factor enabled.
- accounts.utils.is_2fa_required(user: User) bool
Determines whether 2FA is required for specified user account
- accounts.utils.is_2fa_enabled(user: User) bool
Determines whether 2FA is enabled for specified user account
- accounts.utils.send_password_reset(request: HttpRequest, user: User, subject_template_name='registration/password_reset_subject.txt', email_template_name='password_reset_email')
Sends password reset e-mail to selected user if they have an e-mail address. Does nothing if there’s no e-mail address associated with the account.
- accounts.utils.verify_ip(user_ip: str, auditor: Auditor) bool
Verifies given IP address against whitelist associated with user account.
- Parameters:
user_ip – an IP address in string format (e.g. 192.168.1.1)
auditor – user profile instance
- Returns:
Falseif the ip address is not whitelisted.Trueif address is whitelisted, or there is no whitelist
- accounts.utils.verify_request_ip(request: HttpRequest, auditor: Auditor) bool
Calls
verify_ip()using IP address from the request to verify whether the IP address is valid (allowed by whitelist) for this account.- Parameters:
request – current HTTP request
auditor – relevant user profile
- Returns:
Trueif IP address is allowed
- accounts.utils.password_complexity_is_valid(user: User, password: str) bool
Validates password against the user’s current policy.
- accounts.utils.can_user_set_password(user: User) bool
whether user account is allowed to have a password. Some IDPs may prevent user from setting a password if user has logged in using SAML protocol.
- accounts.utils.get_users_who_can_reset_password(**filters) QuerySet
Gets users who can set their password. This excludes public accounts, accounts managed by some SAML IDPs etc
- accounts.utils.get_user_for_username_case_insensitive(username: str, user_model: type[django.contrib.auth.models.User] | type[regions.models.RegionUserLink] = <class 'django.contrib.auth.models.User'>, select_related: ~typing.Iterable[str] | None = None) User | RegionUserLink | None
Gets a user by username disregarding the case for a specified model. If there are case-insensitive duplicate usernames, a case-sensitive match is required.
- Parameters:
username – the username to find a user by.
user_model – the user model to query.
select_related – related field names to select using QuerySet.select_related.
- Returns:
User, RegionUserLink or None
- accounts.utils.string_is_email(string: str) bool
Shorthand method for checking if a string matches the format of an email address. This doesn’t validate if the email address is a real email address.
- Returns:
boolean if the string looks like an email address.
- accounts.utils.login_as_demo(request: HttpRequest, demo_user: User, original_user: User | None = None, return_url: str | None = None)
Login as a demo account in a new session
- accounts.utils.logout_from_demo(request: HttpRequest) str
Logout from demo account and log back into original user
- Parameters:
request –
- Returns:
redirect url after login
- accounts.utils.get_logged_in_as_demo(request: HttpRequest) bool
Checks if current request is a logged in demo account
- accounts.utils.allow_demo_login(request: HttpRequest) bool
Demo Accounts function that returns True if this request is allowed to log in as demo account
- accounts.utils.get_demo_accounts_queryset(request: HttpRequest) AuditorQueryset
Demo Accounts function accessible demo users from the passed request
Action logs
- action_audit.utils.iter_choices(choices: Sequence[Tuple[str, str] | tuple[str, Sequence[Tuple[str, str]]]]) Iterator[Tuple[str, str]]
iterate nested/grouped choices as if it were a flat list
- action_audit.utils.get_log_entry_objects_dict(qs: QuerySet) dict[django.contrib.admin.models.LogEntry, Iterable[tuple[int, django.db.models.base.Model]]]
Dictionary mapping log entries to a set of objects referenced by them. Deleted objects will not yield a model instance
- class action_audit.utils.FormChangesMixin
Supplies
get_form_changesmethod to a view allowing it to extract data for a changelog- get_object_changes(observation: CustomObservation, old_observation: CustomObservation, changed_fields: Sequence[str] = None) Iterator[str]
Iterates over changes from changes between old and new data of the observation for certain fields
- Parameters:
observation – current version of observation
old_observation – old observation
changed_fields –
- Yield:
string representing each difference between the old and current objects custom field values
- get_form_changes(form: BaseForm, ignore_fields: tuple[str, ...] = ()) Iterator[str]
Iterates over changes in the form and yields string representing
- Parameters:
form – a django form
ignore_fields – excludes fields from change message.
- Yield:
string representing each change made by user in the form
- get_field_diff(field: Field, initial: str | int | bool | list | float | date | time | datetime | None | Model | ObservationFile, value: str | int | bool | list | float | date | time | datetime | None | Model | ObservationFile) str
Get the difference between initial and new value as str, for native values (str, int, float) python str() repr will be used, for choice values the label will be used as the repr, for list values comma separated array repr will be used
- class action_audit.utils.BaseActionAuditMixin
Adds
_logmethod to a view that logs user action- log_observation_change(request, obj: CustomObservation, old_obj: CustomObservation, fields: Sequence[str] = ()) LogEntry | None
Logs change action for observation
- Returns:
log entry
- action_audit.utils.log_observation_change(user: User, obj: CustomObservation | CustomSubObservationInstance, old_obj: CustomObservation | CustomSubObservationInstance, fields: Sequence[str] = (), change_message: str = '') LogEntry
Logs change action for observation. Intended for use in places where a request object isn’t available, such as a celery task.
- Parameters:
user – The user who made the change.
obj – The observation whose change is being logged.
old_obj – The old state of the observation before the changes.
fields – The fields that were changed.
change_message – A change message to attach to the log entry.
- class action_audit.utils.BaseActionAuditViewMixin
Mixin for views that adds action logging methods
- log_action(action: int, obj: Model | Sequence[Model], field_names: Sequence[str] = (), comment: str = '')
Logs an arbitrary action
- log_view(obj: Model, comment: str = '')
Logs a view action of a single model instance
- log_view_list(objs: Sequence[Model], comment: str = '')
Logs a view action of multiple model instances
- log_change(obj: Model | Sequence[Model], field_names: Sequence[str] = (), comment: str = '')
Logs change to a model instance
- log_creation(obj: Model | Sequence[Model], field_names: Sequence[str] = (), comment: str = '')
Logs creation of a model instance
- log_deletion(obj: Model | Sequence[Model], comment: str = '')
Log deletion of a model instance. Can be used to log that object was marked as unpublished
- class action_audit.utils.ActionAuditFormMixin
Mixin class for model form views (CreateView, ChangeView) which logs action when form is saved. Also supports form views with formset (multiple forms)
- audit_change_message = 'Updated in Settings'
Change message for when object was edited
- audit_create_message = 'Created in Settings'
Change message for when object was created
- audit.utils.log_action(user: User, obj: Model, message: str | list, action_flag: int = 2) LogEntry
Shortcut to creata a
LogEntryinstance- Parameters:
user – The user performing hte action
obj – the actioned object
message – action description or a list of descriptions (list will be convertedto a json list before saving)
action_flag – the action being taken:
ADDITION,CHANGE,DELETION, or any of the custom flags
- Returns:
Saved
LogEntryinstance
- audit.utils.log_bulk_action(user: User, objs: QuerySet, message: str | list, action_flag: int = 2)
Shortcut to bulk create
LogEntryinstances- Parameters:
user – The user performing hte action
objs – the actioned objects
message – action description or a list of descriptions (list will be converted to a json list before saving)
action_flag – the action being taken:
ADDITION,CHANGE,DELETION, or any of the custom flags
- audit.utils.annotate_impersonation_reason(qs: QuerySet) QuerySet
Annotates impersonation log queryset with
reasonfield containing the reason for impersonation. The reason is extracted fromLogEntrymodel within 1 minute of impersonation.- Returns:
a queryset whose objects have a new
reasontext field
Analytics
- analytics.utils.log_event_lazy(obj: BaseModel | tuple[django.contrib.contenttypes.models.ContentType, int], request: HttpRequest | None = None, platforms: Sequence[str] = (), **kwargs)
Delegate event logging to a Celery job
- analytics.utils.get_unique_tags() set[str]
Unique set of tags in all analytics entries
API
- api.utils.create_subobservation_serializers(model_class: Type[BaseAuditModel], audit_form: AuditForm, first_submission=True) Iterator[Tuple[str, Serializer]]
Creates serializers for model’s subobservations yields tuple of name and serializer instance
- api.utils.create_serializer(observation_model: Type[BaseAuditModel] | CustomSubform | Type[BaseSubObservation], audit_form: AuditForm | None = None, viewset: bool = False, first_submission: bool = True) Type[Serializer]
Creates serializer class for specified model class model_class can be either class of the observaiton model or a custom subobservation instance viewset determines if the serializer is to be used as a viewset’s serializer_class
- api.utils.create_viewset(model_class) Type[BaseObservationViewSet]
Creates ViewSet and serializer for specified model class
- class api.utils.SerializerValidatorAdapter
Helper object that holds a placeholder for serializer instance and its answers for the validator so that validator can be created before data is passed into the serializer.
- class api.utils.UnpublishViewSetMixin
Mixin for DRF ViewSets implementing
DestroyModelMixinthat replaces delete action with unpublish.
- class api.utils.ReversionAPIMixin
Mixin for views extending
ModelViewSetthat logs creation/update/deletion actions using the standard DRF endpoints.This mixin should be included before any mixins that override the perform_create/update/destroy method
eGuides
- eguides.utils.import_section_text(section: Section, directory, filename) str
Imports section content from html file and creates related section images
- eguides.utils.create_eguide_json_file(eguide: Eguide) File
Creates a json file object containing a snapshot of eGuide content
- eguides.utils.check_valid_user(user: User, eguide: Eguide) bool
validates that given user has access to the eGuide
- eguides.utils.flatten_sections(sections: Iterable[dict]) Iterable[SectionDict]
flattens a list of nested sections
- eguides.utils.download_file(link: str) tuple[str, django.core.files.base.File]
Helper method to download a file and prepare for saving to a django model.
- Parameters:
link – The file link to be downloaded.
E-mails
- (celery task)emails.utils._send_mail(subject: str, body_text: str, addr_from: str | None, addr_to: str | Iterable[str], fail_silently=False, attachments=None, body_html=None, connection=None, headers=None, locale=None, bcc: str | Iterable[str] | NoneType = None, reply_to=None)
Sends a multipart email containing text and html versions which are encrypted for each recipient that has a valid gpg key installed.
- emails.utils.send_mail_template(subject: str, template: str, addr_from: str, addr_to: str | Iterable[str], institution: Institution = None, fail_silently=False, attachments=None, context=None, connection=None, headers=None, locale=None, bcc=None, reply_to=None)
Send email rendering text and html versions for the specified template name using the context dictionary passed in.
- emails.utils.send_simple_mail(addr_to: str | Iterable[str], subject: str, message: str | Iterable[str], *, bcc=None, addr_from='MEG <noreply@megit.com>', locale: str | None = None, links: Iterable[tuple[str, str]] = (), institution: Institution | None = None, template: str = 'default')
Uses a generic template to send the e-mail message in a plain text as well as html format. message: string message, or a list of lines of the message Links: add hyperlinks to the e-mail, will be rendered under the content
- emails.utils.strip_message_thread(message: str) str
Given an e-mail message thread including replies, extracts only the latest message (reply)
- emails.utils.resolve_filter_obj(filter_obj: dict, custom_fields: CustomFieldQuerySet, flat_form: bool | None = None) Q
Builds a django Q object representation of JSON answer filters.
- Parameters:
filter_obj – The object defining the filters.
custom_fields – A queryset of custom fields which can be used in filter logic.
flat_form – Whether to return a Q object for filtering just custom observations, sub observations or both.
- emails.utils.build_q_object(filter_obj: list | dict, custom_fields: CustomFieldQuerySet, operator: str | None = 'and', flat_form: bool | None = None) Q
Builds a django Q object representation of nested JSON answer filters.
- Parameters:
filter_obj – The object defining the filters.
custom_fields – A queryset of custom fields which can be used in filter logic.
operator – Operator for handling a list of filters.
flat_form – Whether to return a Q object for filtering just custom observations, sub observations or both.
- emails.utils.send_report_rule_sms_to_recipient_fields(rule: ReportRule, observations_iter: Iterable[CustomObservation], sms_content: str) None
Sends sms content to phone number recipients defined in observation answer data.
- Parameters:
rule – The report rule defining recipient fields.
observations_iter – An iterable of observations.
sms_content – The sms text content to send.
Single sign-on
- external_auth.utils.parse_saml_value(value: list | str | None) str | None
Reads SAML value (normally stored as list). Handles empty list
- external_auth.utils.get_or_create_user(idp: SamlIdentityProvider, saml_auth: OneLogin_Saml2_Auth) User
Gets user account (or creates a new one) and updates their role based on provided SAML object
- external_auth.utils.clean_username(username: str, idp: LDAPIdentityProvider) str
Validates that username can be used
- Parameters:
username – the username
idp – the relevant Identity Provider; used to append unique post-fix
- Returns:
username if valid (unique), or validation error
- external_auth.utils.parse_user_attr(user_attributes: dict, key: str) str | None
attribute values are a list, parse first item if exists.
- external_auth.utils.set_user_attr(user: User, auditor: Auditor, field: str, value: Any) None
Shorthand method for updating either user or auditor. Adds a log message explaining how the object was updated. This method does not save either object. If updating a charfield and the string is longer than max length, it’s sliced to fit.
- Parameters:
user – the User.
auditor – the Auditor.
field – The field to update. The prefix ‘auditor.’ means it’s an auditor field.
value – The value to set.
- external_auth.utils.set_ldap_user_attributes(user: User, auditor: Auditor, user_attributes: dict, attribute_map: dict) None
Updates a user model based on mapped LDAP remote user data. This method can update the user, or auditor, but doesn’t save either.
- Parameters:
user – the User.
auditor – the Auditor.
user_attributes – a dict of user data from LDAP server.
attribute_map – a dict which maps LDAP attributes to user model properties.
- external_auth.utils.set_user_attr_from_map(user: User, auditor: Auditor, field: dict, value: Any, idp: SamlIdentityProvider) None
Shorthand method for updating either user or auditor from attribute map defined values. Adds a log message explaining how the object was updated. This method does not save either object. If updating a charfield and the string is longer than max length, it’s sliced to fit.
- Parameters:
user – the User.
auditor – the Auditor.
field – A dict describing the field and how it should be updated.
value – The value to set.
idp – The SAML identity provider.
- external_auth.utils.get_user_attr_teams(field: dict, value: Any, idp: SamlIdentityProvider) set[megforms.models.Team]
Gets the teams a user should have based on the attribute map
- Parameters:
field – A dict describing the field and how it should be updated.
value – The value to set.
idp – The SAML identity provider.
- external_auth.utils.get_user_attr_patient_data_access(field: dict[str, Any], user_groups: list[str], sync_permissions: str) bool | None
Checks whether a user should have patient data access based on attribute map values. To understand when each boolean is returned and when none is returned please check: #32510 note #28 and #37
- Parameters:
field – The field which may contain the group mapping.
user_groups – groups assigned to the user.
sync_permissions – identity provider sync settings overwrite/add/disabled.
- Returns:
True when user belongs to any group that grants access, False when SAML should revoke access, or None when the mapping does not apply.
- external_auth.utils.update_or_create_scim_user(idp: SamlIdentityProvider, name_id: str, scim_id: str | None, email: str | None, user_kwargs: dict, auditor_kwargs: dict, user: User | None = None) tuple['SamlUser', 'Auditor']
Get or create a user and auditor from SCIM data. This function encapsulates the logic for finding an existing user (by scim_id, name_id, or email) or creating a new one if no existing user is found.
- Parameters:
idp – The saml identity provider.
name_id – The SAML username of the user.
scim_id – The SCIM identifier of the user.
email – The email address of the user. Used to link SCIM accounts with existing accounts.
user_kwargs – Keyword arguments used to create / update the user.
auditor_kwargs – Keyword arguments used to create / update the auditor.
user – The user that will be updated. This will be None when a new SCIM account is being provisioned.
Files
- class files.utils.FileReuploadFormMixin
A mixin for forms containing a file field prompting user to re-add files if validation fails as files are cleared by the browser when page reloads. As per Task #25741, form cannot retain files from previous submission, so the user needs to re-attach the files again.
- property errors
Return an ErrorDict for the data provided for the form.
- class files.imagekit.ImagekitCacheStrategy
Solution to files being closed after saving. Copied from: https://github.com/matthewwithanm/django-imagekit/issues/391#issuecomment-283052935
- class files.imagekit.CacheFileBackend
An extension of
Simplebackend, enables local customization.
- files.imagekit.source_name_as_obfuscated_path(generator)
A legacy file namer that stores thumbnails in a directory per-file, but obfuscates original file name by hashing it
Messaging
- messaging.utils.get_valid_recipients(sender: User, qs: QuerySet = None, include_sender=False, active: bool = True) QuerySet
Given a sender user, returns a queryset of potential recipients they can send messages to.
- Parameters:
sender – the user
qs – Optional queryset. If not given, all users will be considered
include_sender – whether sender should also be considered, or excluded from potential recipients
active – whether to show deactivated or active accounts.
- Returns:
filtered
qsor queryset of userssendercan send messages to
Push messages
- push_messages.utils.send_user_push_message(user: User, message: str, title='MEG') PushResponse | None
Sends simple push notification to specified user’s device. The user is targeted by their id and username (using NamedUser).
User must be logged in to the app to receive messages.
- Parameters:
user – the recipient
message – the message body
title – push notification title
- Returns:
UA push response instance
QIP
- qip.utils.qip_field_choices() Sequence[Tuple[str, str]]
Builds a list of QIP fields that can be included/excluded in QIP preferences
- qip.utils.qip_app_field_choices() Sequence[Tuple[str, str]]
Builds a list of QIP fields that can be included/excluded in QIP preferences Removes choices that are not relevant to the client app
- qip.utils.can_edit_issue(issue: Issue, auditor: Auditor) bool
Tells whether auditor has permission to edit given Issue. It is assumed that user already has read permission, so audit form access or institution membership is not verified. Returns True if user can edit the permission. False is returned if user has only read access.
Regions
- regions.utils.is_upstream_region(request: Request | None) bool
Tells whether given request comes from upstream server. If authentication token is missing or invalid, it is assumed it didn’t come from upstream region.
- Parameters:
request – an authenticated API request from another region
- Returns:
True if request came from upstream region based on authentication token
- regions.utils.is_username_unique(username: str) bool
Checks if username isn’t already registered in this or another region. Returns True if username is not taken (can be used), or False if username is already taken
- regions.utils.make_unique_username(username: str) str
Given a username, update it to make unique globally. Adds and increments a number after username until it’s unique across all regions
- Parameters:
username – a username string
- Returns:
the same username if already unique, or updated username with appended number to make it unique
- regions.utils.generate_upstream_unique_usernames(usernames: dict[str, str]) dict[str, str]
Checks if usernames are unique on upstream servers. The upstream server generates unique usernames and returns a map of original -> unique username. usernames are updated with this data and returned.
- Parameters:
usernames – an initial mapping of usernames mapped to themselves
- Returns:
mutated
usernamesdict with updated values to be unique
- regions.utils.remove_region_user_links(usernames: list[str]) None
Removes users from upstream region. Use to free-up usernames after accounts have been deleted or their creation rolled back.
- Parameters:
usernames – names to be deleted
Dashboards
- exception dashboard_widgets.utils.InvalidFilterError
Exception raised when an invalid filter is passed (e.g. filter field does not exist)
- exception dashboard_widgets.utils.DashboardConfigError(message: str, obj: BaseModel | None = None, *, url: str = '', config_field: str = '')
Exception raised when widgets config is in invalid due to user (configuration) error. For instance, configuration references a field that does not exist, or a required configuration field is not set.
This exception typically means the error can be remediated by the user by editing the
WidgetConfig. However, if the widget is hardcoded, the configuration can not be edited by the user.- Parameters:
message – the message explaining the error (mandatory); can be a lazy-translated string
obj – the object whose configuration caused the error
url – url at which the error occurred (if any)
config_field – the name of the configuration field that caused the error (if any) to help troubleshooting
- dashboard_widgets.utils.get_field_name_lookup(field_name: str) tuple[str, Optional[str]]
Parses field name lookup and returns tuple of actual field name, lookup
- Parameters:
field_name – field name to parse
- Returns:
field name, lookup (if exists otherwise None)
- dashboard_widgets.utils.get_field_names(filters: dict[str, Union[str, int, bool, list, float, NoneType]]) list[str]
Get field names from FiltersDict, also removes lookup from filters key and returns actual field name
- Parameters:
filters –
- Returns:
- dashboard_widgets.utils.get_field(form: AuditForm, field_name: str, include_hardcoded_fields: list[str] = None) CustomField | Field
Gets Field or CustomField or related form CustomField instance by name. Looks for model fields first, then custom fields, then related form custom fields so passing any non-question field (Ward, auditor, date, etc) will result in that model field being returned. If custom field is unpublished, it will still be returned.
- Parameters:
form – Audit form that contains the field
field_name – name of the field, or custom field
include_hardcoded_fields – array of included hardcoded fields
- Returns:
CustomFieldor model field instance- Raises:
KeyError – if field with given name does not exist in the form or any subform or related form.
- dashboard_widgets.utils.get_nested_field(model: Model, field_path: str) Field
Get Nested hardcoded field instance by key
- Parameters:
model – Base model to get nested field from
field_path – nested field key with
- Returns:
model field instance
- Raises:
KeyError – if field with given name does not exist
- dashboard_widgets.utils.get_field_choices(form: AuditForm | None = None, include_related: bool = False, include_hardcoded: bool = True, compliance_only: bool = False, additional_fields: list[str] = None, field_widgets: list[django.forms.widgets.Widget] | None = None, only_compliance: bool = False) list[dashboard_widgets.widgets.types.ChoiceSchema]
For use in widget config schemas. Generates choices for form questions.
- Parameters:
form – A form to generate question choices for. If blank specific model fields will be returned.
include_related – Whether to include fields from related forms or not.
include_hardcoded – Whether to include hardcoded fields or not.
compliance_only – Whether to include compliance fields only or not.
additional_fields – List of additional custom fields, supported fields: “institution”
field_widgets – Optionally specify the custom field widget classes to filter custom fields by. For widget config fields that only support specific data types, you can limit the custom fields to only those that accept the corresponding data type.
only_compliance – Whether to only show compliance custom fields or not.
- dashboard_widgets.utils.get_issue_field_choices(form: AuditForm | None = None, include_hardcoded: bool = True) list[dashboard_widgets.widgets.types.ChoiceSchema]
For use in widget config schemas. Generates choices for issue fields.
- Parameters:
form – A form to generate issue field choices for. If blank specific model fields will be returned.
include_hardcoded – Whether to include hardcoded fields or not.
- dashboard_widgets.utils.get_model_choices(model: Model, dashboard: Dashboard, value_property: str = 'pk') list[ChoiceSchema]
For use in widget config schemas. Generates choices for a model.
- Parameters:
model – The database model to get choices for.
dashboard – The dashboard containing the widget.
value_property – The property name of the value field.
- dashboard_widgets.utils.get_tag_choices(dashboard: Dashboard) list[ChoiceSchema]
Gets choices for tags related to a dashboard.
- dashboard_widgets.utils.get_team_choices(dashboard: Dashboard, value_property: str = 'name') list[ChoiceSchema]
For use in widget config schemas. Generates choices for teams.
- Parameters:
dashboard – The dashboard containing the widget.
value_property – The property name of the value field.
- dashboard_widgets.utils.get_subform_choices(form: AuditForm | None = None) list[dashboard_widgets.widgets.types.ChoiceSchema]
For use in widget config schemas. Generates choices for subforms.
- Parameters:
form – A form to generate subform choices for. If blank no choices are returned.
- dashboard_widgets.utils.is_choice_field(schema: SchemaDict) bool
For use in
WidgetChangeFormto determine if a field schema defines a choice field.
- dashboard_widgets.utils.get_custom_query_fields(form: AuditForm) dict[str, Union[django.db.models.query_utils.Q, Callable[[Union[str, int, bool, list, float, NoneType]], django.db.models.query_utils.Q]]]
Returns custom query fields that are not related to any hardcoded question or CustomField
- Parameters:
form – Audit form
- Returns:
map of custom field annotation name and annotation query either as Q or as callable that returns Q
- dashboard_widgets.utils.filter_observations_or_issues(form: AuditForm, qs: ObservationQuerySet | IssueQueryset, filters: FiltersDict, questions: dict[str, Question], ignore_names=None, query_fields: dict[str, CustomQueryField] = None) ObservationQuerySet | IssueQueryset
Given a queryset of observations and a dictionary of filters, applies filtering to the queryset.
The input filter dictionary can map observation or subobservation field to a value. The filter key must be a valid name of either custom or hardcoded field. The value can be a single value or a list of values (observation having any of the values in the list will be included in the resulting queryset). Empty filters dict will result in the same queryset being returned.
- Parameters:
form – Audit form
qs – observations or issues queryset
filters – Filters map
questions – questions map
ignore_names – ignored fields names
query_fields – custom fields queries map
- Returns:
filtered queryset
- Raises:
InvalidFilterError – if the passed filters are not valid and cannot be evaluated, e.g. the field named in the filter does not exist
- dashboard_widgets.utils.get_dashboard_url(dashboard: Dashboard, *, widget: WidgetConfig | None | int = None, form_type: FormType | None = None, institution: Institution | None = None) str
Generates url to the given dashboard or widget, given preferred form and institution parameters.
- dashboard_widgets.utils.import_widgets()
Ensures that widget classes are imported so that they can be picked up by itersubclasses() when looking for dashboard widget classes. @cache decorator ensures that this function is used only once per python process as subsequent calls would be redundant. This allows widgets to be imported lazily.
- dashboard_widgets.utils.get_user_editable_dashboard_ids(auditor: Auditor) set[int]
For given user profile, returns a set of dashboard ids the user can edit by excluding dashboard they don’t have access to and global (shared) dashboards. Note: This utility does not check if user has edit dashboard permission unpublished dashboards are not excluded. Editable dashboard implies all widgets within the dashboard can be edited as well
- dashboard_widgets.utils.make_date_getter(date_field: Question | None, *observations: ObservationQuerySet) Callable[[Observation], datetime.datetime]
Builds a function that given observation returns its date/time given date field. It caches the results between observations from the same session.
- Parameters:
date_field – date field used to determine date/time. This defaults to session end date. If form uses a custom date field, it must be passed here.
observations – list of observation querysets to pre-load and pre-cache end-date. This will help avoid per-observation queries when invoking the callable.
- dashboard_widgets.utils.is_nan_value(value: Any) bool
Safely check if a value is NaN, handling any input type
- class megforms.calc_utils.Granularity(value, names=<not given>, *values, module=None, qualname=None, type=None, start=1, boundary=None)
Available granularity values copied from: https://www.chartjs.org/docs/latest/axes/cartesian/time.html#time-units commented-out values are currently not supported, but code is kept for future-proofing and to demonstrate the logic behind why specific values are chosen. Lower granularity = smaller time slices = smaller number representation.
- get_truncator_class() type[django.db.models.functions.datetime.TruncBase]
Django QS aggregation truncator class
- get_relativedelta() relativedelta
Get relative delta between keys for each Granularity
- format_datetime(value: Timestamp | datetime) str
Formats datetime object using this granularity and respecting current locale.
- Parameters:
value – a pandas Timestamp, or datetime object. Either naive, or tz-aware is supported
- get_keys_from_daterange(start: date, end: date) Iterator[tuple[datetime.date, datetime.date]]
yields all possible granularity keys between given start and end dates
- megforms.calc_utils.select_granularity(start_date: None | datetime | date, end_date: None | datetime | date, max_days: timedelta | int = datetime.timedelta(days=32)) Granularity
Selects daily / monthly granularity for line chart based on time span between the two dates.
- Parameters:
start_date – start date of the timespan
end_date – end date of the timespan
max_days – max days to show daily granularity, else monthly granularity is used.
- Returns:
GRANULARITY_MONTHorGRANULARITY_DAYdepending on selection
- megforms.calc_utils.get_count(observation: BaseAuditModel) int
Simple function that returns count of passed in observations (1) used to implement calculation function for calculate_over_time
- megforms.calc_utils.make_date_key(date: datetime | date, granularity: Granularity, always_return_date: bool = False) date | int | str
Snaps given date to a start of daterange given granularity (day, month, etc)
- Parameters:
date – date to snap to the start of daterange given granularity
granularity – GRANULARITY_MONTH, GRANULARITY_DAY, or any Granularity value
always_return_date – instead of returning str or int for some granularity, always return a date
- Returns:
first day of the date range where given date belongs
- megforms.calc_utils.calculate_over_time(observations: Iterable[BaseAuditModel], calculation: Callable[[BaseAuditModel], int | float | None], date_getter: Callable[[BaseAuditModel], datetime.date | datetime.datetime] | None = None, granularity=Granularity.month, average: bool | str = True, include_none_compliance: bool = False) ChartValues
Calculates monthly compliance / observation count.
- Parameters:
observations – collection of observations for which data will be calculated
calculation – what type of built-in calculation should be carried out, or a function that accepts observation and returns value
date_getter – function getting date given observation (leave blank to get date from session end time). Callable must return a date object.
granularity – GRANULARITY_MONTH, GRANULARITY_DAY, or a callable returning either to select granularity used to plot days on line chart
average – whether average of values should be calculated for when multiple values fall within one data point. Else, sum will be calculated
include_none_compliance – whether to include null compliance values.
- Returns:
list of tuples (value, month name) where value is a compliance or count depending on arguments passed
- class megforms.calc_utils.BoxPlotValues
- megforms.calc_utils.calculate_boxplot_values(values: list[Union[int, float]], decimal_places: int | None = None) BoxPlotValues
Calculate and return min, first quantile, median, third quantile and max for box plot chart
- Parameters:
values –
decimal_places –
- Returns:
BoxPlotValues dictionary with min, first quantile, median, third quantile and max values
- megforms.calc_utils.get_target_performance_for_observations(observations: tuple[ObservationQuerySet], filters: dict, target_action: int, forms: Iterable[AuditForm]) int
Gets the performance for a given target type for a queryset of observations.
- Parameters:
observations – The observations used to calculate the performance.
filters – A dict to filter the observations queryset.
target_action – The type of target action being assessed.
forms – Audit forms to filter the observation querysets by.
- Returns:
An integer representing the target performance.
- megforms.calc_utils.get_observation_targets(targets: TargetQueryset, start_date: datetime.date, end_date: datetime.date, target_breakdown: str, forms: Iterable[AuditForm], observations: ObservationQuerySet | Iterable[ObservationQuerySet], target_action: int | None = 1) Iterator[TargetValue]
Gets an iterator of TargetValue objects for the given time range, target breakdown, forms, institutions, wards and observations.
- Parameters:
targets – The queryset of targets to compute the data for.
start_date – The start date to get targets for.
end_date – The end date to get targets for.
target_breakdown – For grouping the targets. Can be one of TARGET_BREAKDOWN_WARD, TARGET_BREAKDOWN_USER or TARGET_BREAKDOWN_INSTITUTION.
forms – An iterable of AuditForm objects to filter the targets by.
observations – A QuerySet or an iterable of ObservationQuerySet objects to use when calculating the actual values for the targets.
target_action – The target action.
- Returns:
An iterator of TargetValue objects.
- megforms.calc_utils.multiply_chart_values(multiplier: float | int, values: ChartValues) ChartValues
Multiply each of the chart values by specified value
- megforms.calc_utils.get_distinct_answers(field: Question, *observations: ObservationQuerySet) set[Answer]
Extracts a set of answers given in a set of observations. Returns answer that was used at least once, excludes null values.
- megforms.calc_utils.get_choices(field: CustomField | Field) list[Tuple[str, str]]
Returns a list of choices for choice fields whether it’s a hardcoded or custom field
- megforms.calc_utils.get_choices_or_distinct_answers(field: Question, *observations: ObservationQuerySet) list[Choice | tuple[int, str]]
Distinct choices in a given set of observations. If fields is a choice field, returns all possible choices. Otherwise, returns a list of all answers used across all provided observations.
If field is a foreign key, returns value of the foreign key and label of the related object.
- megforms.calc_utils.create_chart_data(field: Question, *observations: ObservationQuerySet, groupings: dict[str, list[str]] | None = None, compliance: bool = True, total_display: str | None = None, counter_logic: Literal['audits', 'observations'] = 'observations') Iterable[ChartValue]
Generates values for chart widget for a given field. The observations are grouped by answer to the given field, and returned data shows average compliance/count of observations for each of the answers.
For choice fields, the generated dataset will include answers that have no values (None compliance, or 0 count) -
get_choices_or_distinct_answers()is used.Example result for Hand Hygiene profession field:
(100.0, "1.0 Any nurse or midwife") (None, "1.1 Nurse") ... (94.0, '4.15 Non-core allied health/other')
- Parameters:
field – the form field
observations – querysets of observations to use as data source
groupings – Optional dictionary of group name to a list of grouped values. When present, aggregate answers for grouped values into the group name.
compliance – Whether to output compliance (True), or simply counts (False) of observations. NOTE: the returned compliance is in 0-100 range
total_display – format for “total” display beside each item. Leave it null to not show the totals
counter_logic – whether to count by audit sessions or observations.
- Yields:
tuples containing values (compliance or count) and their field names (or date for over time widgets).
- megforms.calc_utils.group_chart_values(values: ChartValues, groupings: dict[str, list[str]], reduce_func=<built-in function sum>) Iterator[ChartValue]
Given a list of chart values and a dictionary, compresses the values by grouping them and summing their values. The dictionary should map a string to a list of values that should be converted to that string. reduce_func can be passed to change how values within the group are aggregated. The function needs to accept an iterable of values and output a single value.
- megforms.calc_utils.get_form_questions(audit_form: AuditForm) list[Union[audit_builder.models.CustomField, django.db.models.fields.Field]]
Gets all questions in given form
Legacy method that may duplicate
AuditForm.get_questions()
Hand Hygiene
- handhygiene.utils.start_date = datetime.datetime(2011, 1, 1, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Dublin'))
Collection period 10 starts in July 2015
- handhygiene.utils.calculate_collection_period(date: date) int
Calculate collection period for the selected date
There are two collection periods per year, in May and October Each collection period number is one higher than the previous one
- Parameters:
date – the datetime object
- Returns:
collection period
- handhygiene.utils.calculate_collection_period_dates(period: int) tuple[datetime.datetime, datetime.datetime]
calculate start and end date of the collection period
- Parameters:
period – collection period number
- Returns:
tuple containing start and end dates
- handhygiene.utils.get_hcw_code_and_profession(hcw_string: str) tuple[str, str]
Split hcw_string into (code, profession) tuple
- Parameters:
hcw_string – str input string, like “1.1 Nurse”
- Returns:
tuple containing hcw code and profession strings, like (“1.1”, “Nurse”) or tuple with empty string and whole input string
- handhygiene.utils.parse_barriers(barriers: str | None) Iterable[str]
Use this utility to parse barriers from text format to a list of selected barriers.
Barriers is a legacy field that stores multiple of elements in a text field. The data can be comma-separated, or encoded as a python literal list.
- Parameters:
barriers – raw value from
barriersfield (can be null)- Returns:
a sequence containing the selected barriers, or an empty sequence if barriers is empty or null
- handhygiene.utils.calculate_barriers_rate(observations: QuerySet) Iterator[tuple[int, str]]
Generates chart data for barriers field. Uses
parse_barriers()to extract individual barrier choices from queryset.- Yields:
tuples containing count and label of each barrier
Translation
- class audit_builder.translation_utils.TranslationModelTabbedFormMetaclass(name, bases, attrs)
- class audit_builder.translation_utils.TranslationTabbedModelForm(*args, data=None, languages: set[str] = None, **kwargs)
A model form that shows tabs on top allowing to switch between language-specific fields. .. highlight:: html+django * Target form must be rendered under tab template. Example:
{% include 'translation/language_tabs.html' %} <div class="card language-card"> <form /> </div>
view class should also extend
TranslationFormViewMixinto hide irrelevant languages from the tab
- property media
Return all media required to render the widgets on this form.
Custom forms
- class audit_builder.utils.CustomValueSerializer(serializer, deserializer, serializer_field_class)
Create new instance of CustomValueSerializer(serializer, deserializer, serializer_field_class)
- deserializer
Alias for field number 1
- serializer
Alias for field number 0
- serializer_field_class
Alias for field number 2
- class audit_builder.utils.WeighedValue(value, weight)
Create new instance of WeighedValue(value, weight)
- value: float | None
Alias for field number 0
- weight: float
Alias for field number 1
- get_percent() float | None
Compliance expressed as a value between 0 and 100 (percent), or null if compliance is null
- audit_builder.utils.WeightedValue
alias of
WeighedValue
- audit_builder.utils.get_model_field_models() Tuple[type, ...]
Models available to model select and model multiselect fields
- audit_builder.utils.get_observation_url(observation: ObservationOrSubObservation, audit_form_id: int, institution_slug: str) str
url of the review page for this issue’s observation. The url will work only for custom observations
- audit_builder.utils.get_model_content_type_choices() QuerySet
Queryset of ContentType instances for models available for model fields
- audit_builder.utils.get_remote_form_model_content_type_choices() QuerySet
Queryset of ContentType instances for models available for remote form select fields
- exception audit_builder.utils.Incompliant
Thrown when the whole observation is considered incompliant
- audit_builder.utils.to_boolean(value: str | bool | None) bool | None
Interprets string value as a boolean
- audit_builder.utils.deserialize_phone_number(value: str | int | bool | list | float | None, region: str | None = None) PhoneNumber | None
Converts a phone number string to a phone number python class. Allows you to specify the two-letter country code indicating how to interpret regional phone numbers.
- Parameters:
value – The value to parse.
region – The country code used to interpret regional phone numbers
- class audit_builder.utils.RemoteFormModelChoiceSerializer(*args, **kwargs)
This field will accept and validate an annotated value field for a customobservation model, but it will return the value instead of returning a model instance
When a field is instantiated, we store the arguments that were used, so that we can present a helpful representation of the object.
- to_internal_value(data)
Transform the incoming primitive data into a native value.
- to_representation(obj)
Transform the outgoing native value into primitive data.
- audit_builder.utils.serialize_value(field_type: type, value: Answer) SerializedAnswer
Turns python value into database text representation
- audit_builder.utils.deserialize_value(field_type: type, value, **kwargs)
Turns database text representation into python value
- audit_builder.utils.create_serializer_field(field: CustomField, serializer: AnswerContainerOrCallable) Field
Creates DRF serializer field instance for specified CustomField
- Parameters:
field – CustomField instance
serializer – the serializer object
- audit_builder.utils.make_model(model: ModelType, form: AuditForm, **kwargs) Model
Creates a dummy instance of model, related to the given form. This ensures that:
dummy answers are valid and contain valid auditor/room etc choice
no additional dummy institutions or forms are created as a result of creating a dummy object
- Parameters:
model – model class
form – form instance to query related choices from.
kwargs – any additional arguments for model baker factory
- Returns:
model instance, or a list of models if _quantity was set
- audit_builder.utils.gen_multichoice(choices: Iterable[Tuple[str, str]], required: bool) Callable[[], List[str]]
Generates a multichoice selection from a repeated list of choices Choices can be repeated to increase their probability. The number of choices returned will be random between 1 (or 0 if required=False) and the number of unique choices
- Parameters:
choices – an iterable of choices
required – whether the result must be a non-empty list
- Returns:
a callable that can be invoked to generate a random set of choices
- audit_builder.utils.generate_value(custom_field: CustomField, serialize=False) Answer | SerializedAnswer
Used to generate values for model baker
- audit_builder.utils.is_choice_field(field_type)
Tells whether specified form field class accepts or requires ‘choices’
- audit_builder.utils.is_model_field(field_type)
Checks whether selected form field class uses model instances as choices
- class audit_builder.utils.AverageCalculator(sum=0.0, count=0.0)
Class used to calculate average value from a number of values with varying weights
- add(value: float | None, weight: float = 1.0)
Add item to the calculator
- Parameters:
value – value (None values will be ignored)
weight – weight of the value
- get_average() float | None
Calculates average value from given values
- Returns:
average value or None if no entries were added
- get_average_percentage() float | None
Average value, multipled by 100
- property weighed_value: WeighedValue
Represents result of this calculation as a weighted average used for further calculations and comparison
- audit_builder.utils.avg_or_none(values: Iterable[float | None]) float | None
Returns average of values in the list, or None if list is empty. Any null values are excluded
NOTE: If you know that your input does not contain nulls, use the more efficient
statistics.fmean()directly, but you will also have to handlestatistics.StatisticsErrorif your dataset is empty.- Parameters:
values – any iterable of numbers, may contain nulls
- Returns:
average of passed values
- audit_builder.utils.avg_or_none_weighted(values: Iterable[tuple[float | None, float]]) float | None
Computes a weighted average of values in the iterable.
- Parameters:
values – the iterable containing tuple (value, weight) where value can be a number of null, and weight is a number.
- Returns:
average of values, taking into account weight of each item
- audit_builder.utils.average_dict(dicts: Iterable[dict[str, float | None]], keys: Sequence[str] | None = None) dict[str, audit_builder.utils.WeighedValue]
Given multiple dictionaries mapping string to a value, returns a single dictionary mapping each key to average value from provided dicts.
Mapped average value is an average of values, weighed by number of non-null values.
- Parameters:
dicts – any iterable containing dictionaries mapping string to a floating point or None
keys – an optional list of tuple containing keys to extract. Leave null to extract all keys. Empty keylist will return an empty dict.
- Returns:
dictionary mapping keys to average value from all provided dicts. The result is not guaranteed to contain a mapping for each of the specified
keys- empty values are not included
- audit_builder.utils.average_tuples(values: Iterable[tuple[str, float | None]]) dict[str, audit_builder.utils.WeighedValue]
Calculates average value grouped by a key from an iterable of tuples.
Example:
>>> average_tuples([('key1', 1.0), ('key1', 0.0), ('key2', 1.0)]) { 'key1': WeightedValue(0.5, 2.0), 'key2': WeightedValue(1.0, 1.0), }
- Parameters:
values – tuples containing a repeatable key and a value
- Returns:
dictionary mapping each encountered key to average value of its values and weight representing number of non-null items
- audit_builder.utils.count_answers(observations: ObservationQuerySet, field: Question, counter_logic: Literal['audits', 'observations'] = 'observations') int
If counting observations counts how many observations and subobservations have an answer to the given field. If counting audits counts the number of audits which have a observations or subobservations with the question answered.
Exclude is used here so we find observations & sub observations with the question answered by filtering out those that don’t.
This function is used for custom fields in custom_answers and also database fields for the audit form like auditor, ward etc. If the field is not a custom field and its in the CustomObservation its a db field so we query it directly.
- Parameters:
observations – A QuerySet of audit observations.
field – The field to check for an answer. Can be a custom field or a hardcoded question or hardcoded field.
counter_logic – Determines the counting strategy (‘audits’ or ‘observations’).
- Returns:
An integer representing either the count of unique audits or the total count of observations with an answer, based on the counter_logic.
- audit_builder.utils.parse_choices(raw_choices: str) Iterable[Tuple[str, str]]
Parse raw choices and yield tuples of (value, display_value)
- Parameters:
raw_choices – string of raw choices separated by newline where display value is separated from db value by comma “,”
- audit_builder.utils.get_subobservations(observation: Observation, subform_or_id: int | 'CustomSubform') Iterable[SubObservation]
Gets queryset of sub_observations of specific type for the observation
- Parameters:
observation – an observation instance (must be a CustomObservation to get a custom sub-observation instance).
subform_or_id – an instance of SubObservation (or it’s id)
- Returns:
QuerySet of sub-observations. Ordinarily it would return a sub-observation instance, but because some subforms are allowed multiple instances per observation, a collection needs to be returned. This covers all 3 possible edge cases: No SubObservation, 1 SubObservation and multiple SubObservations.
- audit_builder.utils.get_answers_with_observation(custom_field: CustomField, observation: Observation) Iterator[tuple[ObservationOrSubObservation, SerializedAnswer]]
Gets answer value from observation for specified field. Handles sub-observation traversal if custom field belongs to a subobservation.
If the observation is annotated with
subform_answersuses the annotation instead of querying sub-observations. Useannotate_subform_answers()to annotate the queryset.- Yields:
tuples containing the sub/observation with the value and the value itself
- audit_builder.utils.get_answers(custom_field: CustomField, observation: Observation) tuple[SerializedAnswer, ...]
Gets answer value from observation for specified field. Handles sub-observation traversal if custom field belongs to a subobservation.
- Returns:
a tuple containing the answer(s)
- audit_builder.utils.choices_to_string(choices: Sequence[Tuple[str, str] | str]) str
Converts list of choices - (value, display) tuple - to a raw_choices multiline text
- Parameters:
choices – sequence of tuples (value, display)
- Returns:
Choices encoded into multiline text string
- audit_builder.utils.create_name(label: str, reserved_names: Iterable[str], form_field_name: str | None = None)
Turns human-readable name to a name usable as field or subform name.
- Parameters:
label – Field or subform label
reserved_names – sequence of names that are already used
form_field_name – name of the form field this function is used on, used for displaying an accurate error message
- Returns:
string that represents base_name in lower-case, without special characters (except for _) and does not collide with reserved names.
- audit_builder.utils.is_compliance_field(field: Question) bool
Tells whether field has compliance data
- audit_builder.utils.get_fields(form: AuditForm, field_names: Collection[str]) Iterable[Question]
Given audit form and field names, generates field instances (custom or hardcoded)
- audit_builder.utils.get_issue_fields(form: AuditForm, field_names: Collection[str]) Iterable[Field | 'CustomIssueField']
Given audit form and field names, generates issue field instances (custom or hardcoded)
- audit_builder.utils.get_subform_label(subform: Subform) str
Given a subform (hardcoded or custom), returns its human-friendly name
- audit_builder.utils.compress_observations(session: AuditSession) Dict[int, Set[int]]
Moves all sub-observations to a single observation if possible without creating duplicate subforms where not allowed. Used to convert a default audit to accordion audit while keeping structure consistent with how accordion audits structure observations.
- Returns:
map of moved subobservations
- audit_builder.utils.make_observation_file_link(observation: ObservationOrSubObservation, field_name: str, file_index: int, file_path: str, as_thumbnail: bool = False) str
Create absolute url link for given media file with its filename as the label.
- Parameters:
path – relative path to media file
- Returns:
<a> tag fith filename in its title, linking to the selected file
- audit_builder.utils.sanitize_filename(path: str) partial[str]
Returns a callable which is used to create a clean path in FileField.upload_to
- audit_builder.utils.humanize_number(number: float | int, decimal_places: int | None = None) str
Converts a number into a human-readable format with commas every three digits.
- Parameters:
number – the number to be converted.
decimal_places – If None, number is converted to int.
- Returns:
string of the humanized number.
- audit_builder.utils.get_subform_fields(*subforms: Subform) Iterator[Question]
Gets questions for all provided subforms. Filters out unpublished subforms. Does not account for subforms excluded in audit form config Supports subforms across different audit forms.
- audit_builder.utils.is_iexact(value: str | int | None, expected_value: str | int | None) bool
Case-insensitive exact match between value and expected_value
- Parameters:
value – value to check
expected_value – the expected value to check for
- Returns:
- audit_builder.utils.is_icontains(container: str | int | None, member: str | int | None) bool
Case-insensitive check if member is contained by container
- Parameters:
member – value to check
container – container that should contain the value
- Returns:
- audit_builder.utils.evaluate_session_date_condition(condition: Condition, custom_answers: dict[str, Answer], session: AuditSession) bool
Checks if the condition’s field has the same date as session.endtime.
- Parameters:
condition – The condition dictionary.
custom_answers – The dictionary containing custom answers.
session – The audit session object.
- Returns:
True if the condition field’s answer is the same date as session.endtime.
- class audit_builder.utils.RelatedObservationDataWrapper(config: FormRelatedData, observations: Iterable[CustomObservation])
Wrapper for related observation data
- audit_builder.utils.populate_filter_placeholders(filters: FiltersDict, auditor: 'Auditor' | None = None) dict[str, SerializedAnswer]
Modifies provided filters dictionary by populating placeholders with concrete data. Note that input dictionary is modified and the same dictionary instance is returned.
- audit_builder.utils.filter_condition_for_field_names(condition: Condition, field_names: list[str]) bool
Recursive helper method for finding a field name in a list of conditions. Supports checking nested conditions.
- Parameters:
condition – The condition object to check.
field_names – The list of field names to check for in the condition.
Links observations via common linkable field values. Removes model links when the linkable field value changes.
- Parameters:
instance – The observation or sub observation to update the model links for.
created – Is true if the instance was just created.
LFPSE
- audit_builder.lfpse.utils.clean_property_id(property_id: str) str
Strips property id of useless meta data which has no meaning outside of the API: ‘Extension.extension:InvolvedAgents.valueCode’ to ‘InvolvedAgents’. ‘AdverseEvent.description’ to ‘description’. This allows it to map to the LFPSE taxonomy spreadsheet.
- class audit_builder.lfpse.utils.CustomFieldCache(field, question_schema, parent_accordion_conditional_show_logic, required)
Create new instance of CustomFieldCache(field, question_schema, parent_accordion_conditional_show_logic, required)
- field
Alias for field number 0
- parent_accordion_conditional_show_logic
Alias for field number 2
- question_schema
Alias for field number 1
- required
Alias for field number 3
- audit_builder.lfpse.utils.convert_calc_logic(calc_logic: dict, custom_fields_map: dict[str, audit_builder.lfpse.utils.CustomFieldCache]) dict
Updates calc logic defined in question_flow.py to contain the correct field names.
- audit_builder.lfpse.utils.convert_conditional_logic(field_cache: CustomFieldCache, organization: str, custom_fields_map: dict[str, audit_builder.lfpse.utils.CustomFieldCache]) list[dict]
Converts question flow schema conditional logic: - swaps the property id for the newly generated custom field names in the fields map. - update set value placeholder with actual value from the database. - set make required for hidden required fields.
- Returns:
list of conditions
- audit_builder.lfpse.utils.get_help_text_from_taxonomy(field_taxonomy: Series) str
Helper method for getting the full help text for a field.
- Parameters:
field_taxonomy – A pandas series of the field’s taxonomy.
- Returns:
The help text as a str.
- audit_builder.lfpse.utils.get_taxonomy() DataFrame
Loads LFPSE field data into a dataframe
- audit_builder.lfpse.utils.build_field_links(form_link: LFPSEFormLink) dict[str, audit_builder.lfpse.models.LFPSEFieldLink]
Uses
LFPSEFieldBuilderto create form links, and deletes any outdated links.- Parameters:
form_link – The form whose links should be built
- Returns:
mapping of each field’s
property_idto itsLFPSEFieldLinkinstance.
- audit_builder.lfpse.utils.build_form_from_schema(form_link: LFPSEFormLink, fields_map: dict[str, audit_builder.lfpse.models.LFPSEFieldLink]) tuple[int, int]
Updates form questions based on LFPSE resources
- audit_builder.lfpse.utils.get_organization_choices(backend: LFPSEBackend) list[Tuple[str, str]]
Downloads ODS codes for all organizations from LFPSE and caches them for subsequent calls. Each client (API key) has a separate cache.
- Parameters:
backend – the back-end config containing a valid token
- Returns:
json object containing all available organizations
- Raises:
HTTPError – if a HTTP error occurred.
JSONDecodeError – If the response body does not contain valid json.
- audit_builder.lfpse.utils.load_default_organization_choices() list[Tuple[str, str]]
Loads organizations from a hardcoded file. The file is good for initial setup of institutions and choices can be further narrowed-down in set-up
- Returns:
list of organization choices
Query utils
- class utils.query.SubqueryCount(queryset, output_field=None, **extra)
Custom Count function to just perform simple count on any queryset without grouping using
OuterRefand other subquery specific function.Usage:
>>> # Counting CustomField for each AuditForm >>> forms_qs = AuditForm.objects.annotate( >>> fields_count=SubqueryCount( >>> CustomField.objects.filter(audit_form_id=OuterRef('pk')) >>> ) >>> )
- output_field = <django.db.models.fields.PositiveIntegerField>
- class utils.query.ArraySubquery(queryset, output_field=None, **extra)
Wraps subquery in an array so that multiple values can be returned
For example, to add an array of field names to an AuditForm queryset:
AuditForm.objects.annotate( field_names=ArraySubquery(CustomField.objects.filter(audit_form_id=OuterRef('pk')).values('field_name')), )
For some data types it may be necessary to override
output_field:comments: CommentQuerySet = Comment.objects.published().annotate( field_comments=JSONObject(field_name=F('field_name'), comment=F('comment')) ).values('field_comments') sub_observations.annotate(comments=ArraySubquery(comments.subquery(observations), output_field=ArrayField(JSONField())))
Important
The subquery must return exactly one field
Note
Order of the elements in subquery is not reflected in the resulting array
See also
Equivalent function is available in newer version of Django: https://docs.djangoproject.com/en/4.2/ref/contrib/postgres/expressions/#django.contrib.postgres.expressions.ArraySubquery
- utils.query.bulk_delete(objs: QuerySet, chunk_size: int = 1000) int
Deletes large amounts of objects from database in smaller chunks to avoid memory errors
Note
This operation is intentionally non-atomic. If deletion is interrupted, it can be re-started with the same queryset to continue deletion. If this behaviour is not desired, wrap the function call in
transaction.atomic.- Parameters:
objs – QuerySet of objects to delete
chunk_size – Number of objects to delete at a time
- Returns:
total number of deleted objects in the queryset (including related objects with on_delete=CASCADE that have been deleted as a result)
- class utils.query.UnnestJson(*args, **kwargs)
Expands a JSON list [1, 2, 3] into separate rows: 1, 2, 3.
Uses PostgreSQL’s
jsonb_array_elementsfunction to transform a JSONB array into a set of rows, where each element becomes a separate row.https://www.postgresql.org/docs/15/functions-json.html
- output_field = <django.db.models.fields.json.JSONField>
- class utils.query.JsonPathExists(*args, **kwargs)
Checks if a JSON path returns any items for the given JSONB field. Returns True if the path matches, False otherwise.
https://www.postgresql.org/docs/15/functions-json.html
Wraps: jsonb_path_exists(target, path)
- output_field = <django.db.models.fields.BooleanField>
- arity = 2
Takes two arguments (field_name, path_string). increment to enable passing more arguments as per postgres function spec
Permission utils
- accounts.perm_update.get_user_forms(auditor: Auditor) AuditFormQueryset
Gets forms accessible by given auditor in their institutions
Important
this utility ignores global/superusers
- accounts.perm_update.get_user_wards(auditor: Auditor) WardQuerySet
Gets wards available to the user in their institution.
Important
this utility ignores global/superusers
- accounts.perm_update.get_base_user_folder_rules(auditor: Auditor) FolderPermissionRuleQueryset
Get base user folder rules for building cache based of
- accounts.perm_update.get_user_folders(auditor: Auditor) FolderQuerySet
Get folders that user has access to based on folder rules and permissions
- accounts.perm_update.update_user_permission_cache(auditor: Auditor, forms: Sequence[AuditForm] | None = None, update_global: bool | None = None)
Re-generates user’s permission cache and stores them in the
AuditorPermissionCachemodel.- Parameters:
auditor – Auditor whose form access should be updated
forms – limit scope of the update to just the selected forms. Leave
Noneto update all forms. Blank means no forms will be updated.update_global – whether to also update user’s global (non-form specific) permissions. Leave
Noneto only update global when no forms are passed (empty, but non-null).
- accounts.perm_update.update_users_perm_cache(auditors: Iterable[Auditor])
Utility to update permissions for multiple user accounts
- accounts.perm_update.add_folder_to_permission_cache(auditor: Auditor, folder: Folder)
Creates or updates a cache entry for a newly created folder with permissions from its parent folder.
This is a lightweight alternative to update_folder_permission_cache() for when a single folder is created and needs to be immediately accessible without rebuilding the entire cache. This prevents 404 errors when users are redirected to newly created folders before the permission cache catches up.
For nested folders, permissions are inherited from the parent folder’s cache. For top-level folders (where parent_id is null), permissions are inherited from the global cache entry. If no parent/global cache entry exists, the cache entry won’t be created - this is expected for users without folder access.
- Parameters:
auditor – Auditor who owns the newly created folder
folder – Folder to add to the cache
Workflow utils
- megworkflows.utils.replace_placeholders_with_original_types(template: str, data: dict) str | int | float | None
Replaces placeholders in the template with values from the data dictionary, preserving original types and supporting dot notation for nested dictionaries. this function maintains data types and allows nested key access.
- megworkflows.utils.process_custom_answers(config: CreateObservationConfig, trigger_observation: CustomObservation, choice_placeholder_value: str | None = None) dict
Process custom answers by replacing placeholders in the template strings with values from the trigger_observation custom answers.
- Parameters:
config – Operation configuration containing custom_answers templates.
trigger_observation – Observation that triggered the workflow.
choice_placeholder_value – Optional value to replace {choice_value} placeholders.
- Returns:
Processed custom_answers dictionary with resolved values.
- megworkflows.utils.create_observation(workflow_id: int, operation: Operation, trigger_observation: CustomObservation, change_time: datetime | None = None, choice_placeholder_value: str | None = None, audit_form_id: str | None = None) CustomObservation | None
Create an observation using the provided configuration and return it.
- Parameters:
workflow_id – ID of the Workflow initiating the observation creation.
operation – The Operation object that defines the type of observation to create.
trigger_observation – The CustomObservation object that triggered this observation creation.
change_time – Optional datetime representing the time at which the observation change is performed.
choice_placeholder_value – Optional value to replace {choice_value} placeholders.
audit_form_id – Optional audit form id whose observation should be created, skips fetching from operation.config
- Returns:
A new CustomObservation object if creation is successful; otherwise, None.
- megworkflows.utils.update_observation(workflow_id: int, operation: Operation, trigger_observation: CustomObservation, observation: CustomObservation) CustomObservation
Update an observation’s answers using the provided configuration and return it. Runs custom field calculation on the updated answers, then saves observation.
- Parameters:
workflow_id – The pk of the workflow.
operation – The operation object.
trigger_observation – The observation that triggered the workflow.
observation – The observation to be updated.
- megworkflows.utils.query_exists_for_conditions(conditions: list, form_id: int, observation: CustomObservation, operator: str) bool
Check if a record exists in the database that matches the given conditions. It uses the gte operator to include submissions created on the same day as the trigger observation. This ensures that matching records from the same day are considered valid.
- Parameters:
conditions – The list of conditions to evaluate.
form_id – The ID of the form to query against.
observation – The observation object to use in the query.
operator – The operator to use in the query.
- Returns:
True if a matching record exists, False otherwise.
- megworkflows.utils.evaluate_answer_equal(conditions: dict[str, Any], observation_id: int) bool
Evaluates if the custom answer for a given observation matches the expected answer. This function is used to check whether a specific field, identified by field_name in the observation-related data, matches the provided answer.
- Parameters:
conditions – A dictionary containing the conditions for evaluation.
observation_id – An integer ID of the observation to be evaluated.
- Returns:
Boolean value indicating whether the custom answer matches the expected one.
- megworkflows.utils.evaluate_answer_changed(filters: dict[str, Any], observation_id: int) bool
Evaluate whether the answer in question has changed to the desired value in order to process the workflow
- Parameters:
filters – the filter conditions to evaluate
observation_id – id of the trigger observation
- Returns:
False if the answer has changed to the desired answer; else True
- megworkflows.utils.evaluate_query_filter_conditions(filters: Dict[str, Any], observation_id: int) bool
Evaluate the filter conditions against the observation.
- Parameters:
filters – The filter conditions to evaluate.
observation_id – The observation object to check against.
- Returns:
True if all conditions are met, False otherwise.
- megworkflows.utils.render_template_with_placeholders(template: str, data: dict, default_values: dict = None) str
Renders a template string by replacing placeholders with values from the data dictionary. This function uses the existing replace_placeholders_with_original_types function to resolve values. Converts non strings to strings before adding the value to the template.
- Parameters:
template – The template string containing placeholders.
data – The data dictionary containing values to replace placeholders.
default_values – Dictionary of default values for specific placeholders.
- Returns:
The rendered string with placeholders replaced.
- megworkflows.utils.create_trigger_object_data(trigger_object: CustomObservation) dict
Converts the trigger object to a dict for use in workflows.
- Parameters:
trigger_object – The object to be converted into a dict.
- megworkflows.utils.is_empty_or_none(value: str) bool
Helper function to check if a value is None, empty, or whitespace-only or null_placeholder.
- megworkflows.utils.trigger_email_notification(config: EmailConfig, observation: CustomObservation) None
Trigger email notification using send_simple_mail with templated content. This function retrieves an observation, prepares a data context, and sends an email with dynamically rendered content using placeholders.
- Parameters:
config – Configuration dictionary for email composition.
observation – The CustomObservation object that triggered this observation creation.
- megworkflows.utils.trigger_sms_notification(config: SMSConfig, observation: CustomObservation) None
Trigger SMS notification using send_sms_to_number with templated content.
- Parameters:
config – Configuration dictionary for SMS composition.
observation – The CustomObservation object that triggered this observation creation.
- megworkflows.utils.handle_create_observation_for_each_choice(workflow_id: int, operation: Operation, trigger_observation: CustomObservation, change_time: datetime | None = None) list['CustomObservation']
Creates multiple observations, one for each selected choice in the specified multi-choice field.
If creating observations for different forms (audit_forms is set) uses audit_forms to get form IDs rather than using default (audit_form_id in config)
- Parameters:
workflow_id – ID of the Workflow that triggered the process.
operation – The operation object containing configuration with choice_field_name.
trigger_observation – The observation that triggered the workflow.
change_time – Time at which this operation is performed.
- Returns:
A list of new CustomObservation objects.
- megworkflows.utils.trigger_web_request(config: RequestConfig, observation: CustomObservation) None
Trigger web requests that will use the url, auth and request body from the config object after the creation/updating of the relevant observation Does not support related data from forms with nested subforms
- Parameters:
config – Configuration dictionary for request objects
observation – The CustomObservation object that triggered this observation creation.
- Returns:
- megworkflows.utils.collect_subform_answers(observation: CustomObservation, custom_answers: dict, linked_choice_fields: dict[str, str] | None = None) None
Helper function to collect all subform answers for an observation. Validates that each custom field is published and actually valid for the current subform before adding to the custom answers.
- Parameters:
observation – The observation to collect subform answers from
custom_answers – Dictionary to update with collected answers
linked_choice_fields – Optional dict with ‘single_choice_field_name’ and ‘multi_choice_field_name’ to link fields for pairing
- megworkflows.utils.append_object_mapping(trigger_object: CustomObservation, object_mapping: dict) dict
Extracts values from trigger_object based on dot notation paths
- Parameters:
trigger_object – The source object to extract values from
object_mapping – Dictionary mapping dot notation paths to target keys
- Returns:
Dictionary with resolved values
- megworkflows.utils.convert_time(time_str: str, time_format: str) str
Converts time from HH:MM:SS to another format.
- megworkflows.utils.prepare_xml_request(config: dict, custom_answers: dict, resolved_mapping: dict = None) str
Parses XML template and applies field mapping to create the final XML payload. It supports simple tag names or XPath expressions in the field mapping to handle nested or non-unique tags. For simple tags, it maintains backward compatibility by updating all matching tags. For XPath expressions, it allows precise targeting of elements.
- Parameters:
config – The Operation configuration object.
custom_answers – Values from the observation to be inserted into the template
resolved_mapping – Optional pre-resolved values to apply directly
- Returns:
Formatted XML string
Document editor
- editor.revision_utils.update_revision_history(instance: Model, revisions_data: list[editor.types.RevisionData])
Parses list of revisions from CKEditor and creates/updates revision history accordingly.
Objects are created if their ID does not already exist. Or updated if object with that ID already exists and is associated with the same
instance. Subset of fields can be updated (for example onlynameif user renamed the revision).- Parameters:
instance – object whose revision is being updated
revisions_data – revision data received from CKEditor