Source code for crate_anon.crateweb.userprofile.models

"""
crate_anon/crateweb/userprofile/models.py

===============================================================================

    Copyright (C) 2015, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CRATE.

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

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

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

===============================================================================

**Extended user profile for Django, with all our user configuration details.**

"""

from typing import Any, List, Optional, Type, TYPE_CHECKING

from cardinal_pythonlib.django.fields.jsonclassfield import JsonClassField
from django.conf import settings
from django.db import models
from django.dispatch import receiver
from django.http.request import HttpRequest

from crate_anon.crateweb.core.constants import (
    LEN_ADDRESS,
    LEN_PHONE,
    LEN_TITLE,
)
from crate_anon.crateweb.extra.salutation import (
    forename_surname,
    salutation,
    title_forename_surname,
)

if TYPE_CHECKING:
    from crate_anon.crateweb.research.models import PatientMultiQuery


# =============================================================================
# User profile information
# =============================================================================


[docs]class UserProfile(models.Model): """ User profile information. This is used for: - stuff the user might edit, e.g. per_page - a representation of the user as a researcher (or maybe clinician) """ user = models.OneToOneField( settings.AUTH_USER_MODEL, primary_key=True, on_delete=models.CASCADE, related_name="profile", ) # https://stackoverflow.com/questions/14345303/creating-a-profile-model-with-both-an-inlineadmin-and-a-post-save-signal-in-djan # noqa # first_name: in Django User model # last_name: in Django User model # email: in Django User model N_PAGE_CHOICES = ( (10, "10"), (20, "20"), (50, "50"), (100, "100"), (200, "200"), (500, "500"), (1000, "1000"), ) N_PATIENT_PAGE_CHOICES = ( (1, "1"), (5, "5"), (10, "10"), (20, "20"), (50, "50"), (100, "100"), ) # ------------------------------------------------------------------------- # Web site personal settings # ------------------------------------------------------------------------- per_page = models.PositiveSmallIntegerField( choices=N_PAGE_CHOICES, default=50, verbose_name="Number of items to show per page", ) patients_per_page = models.PositiveSmallIntegerField( choices=N_PATIENT_PAGE_CHOICES, default=1, verbose_name="Number of patients to show per page " "(for Patient Explorer view)", ) line_length = models.PositiveSmallIntegerField( default=80, verbose_name="Characters to word-wrap text at in results " "display (0 for no wrap)", ) collapse_at_len = models.PositiveSmallIntegerField( default=400, verbose_name="Number of characters beyond which results field starts " "collapsed (0 for none)", ) collapse_at_n_lines = models.PositiveSmallIntegerField( default=5, verbose_name="Number of lines beyond which result/query field starts " "collapsed (0 for none)", ) sql_scratchpad = models.TextField( verbose_name="SQL scratchpad for query builder" ) patient_multiquery_scratchpad = JsonClassField( verbose_name="PatientMultiQuery scratchpad (in JSON) for builder", null=True, ) # type: PatientMultiQuery # ------------------------------------------------------------------------- # Developer # ------------------------------------------------------------------------- is_developer = models.BooleanField( default=False, verbose_name="Enable developer functions?" ) # ------------------------------------------------------------------------- # Contact details # ------------------------------------------------------------------------- title = models.CharField(max_length=LEN_TITLE, blank=True) address_1 = models.CharField( max_length=LEN_ADDRESS, blank=True, verbose_name="Address line 1" ) address_2 = models.CharField( max_length=LEN_ADDRESS, blank=True, verbose_name="Address line 2" ) address_3 = models.CharField( max_length=LEN_ADDRESS, blank=True, verbose_name="Address line 3" ) address_4 = models.CharField( max_length=LEN_ADDRESS, blank=True, verbose_name="Address line 4" ) address_5 = models.CharField( max_length=LEN_ADDRESS, blank=True, verbose_name="Address line 5 (county)", ) address_6 = models.CharField( max_length=LEN_ADDRESS, blank=True, verbose_name="Address line 6 (postcode)", ) address_7 = models.CharField( max_length=LEN_ADDRESS, blank=True, verbose_name="Address line 7 (country)", ) telephone = models.CharField(max_length=LEN_PHONE, blank=True) # ------------------------------------------------------------------------- # Clinician-specific bits # ------------------------------------------------------------------------- is_clinician = models.BooleanField( default=False, verbose_name="User is a clinician (with implied permission to look " "up RIDs)", ) is_consultant = models.BooleanField( default=False, verbose_name="User is an NHS consultant " "(relevant for clinical trials)", ) signatory_title = models.CharField( max_length=255, verbose_name='Title for signature (e.g. "Consultant psychiatrist")', ) # ------------------------------------------------------------------------- # Functions # -------------------------------------------------------------------------
[docs] def get_address_components(self) -> List[str]: """ Returns the user's address lines. """ return list( filter( None, [ self.address_1, self.address_2, self.address_3, self.address_4, self.address_5, self.address_6, self.address_7, ], ) )
[docs] def get_title_forename_surname(self) -> str: """ Returns the user's name in the form "Dr Joe Bloggs". """ # noinspection PyTypeChecker,PyUnresolvedReferences return title_forename_surname( self.title, self.user.first_name, self.user.last_name )
[docs] def get_salutation(self) -> str: """ Returns a salutation for the user (e.g. "Dr Bloggs"). """ # noinspection PyTypeChecker,PyUnresolvedReferences return salutation( self.title, self.user.first_name, self.user.last_name, assume_dr=True, )
[docs] def get_forename_surname(self) -> str: """ Returns the user's name in the form "Joe Bloggs". """ # noinspection PyUnresolvedReferences return forename_surname(self.user.first_name, self.user.last_name)
# noinspection PyUnusedLocal
[docs]@receiver(models.signals.post_save, sender=settings.AUTH_USER_MODEL) def user_saved_so_create_profile( sender: Type[settings.AUTH_USER_MODEL], instance: settings.AUTH_USER_MODEL, created: bool, **kwargs: Any ) -> None: """ Django signal receiver. Called when a Django User object has been saved. Attaches Args: sender: the model class (User) instance: will be the User object created: was a new record created? **kwargs: other arguments we don't care about See https://docs.djangoproject.com/en/2.1/ref/signals/#post-save. """ UserProfile.objects.get_or_create(user=instance)
# ============================================================================= # Helper functions # =============================================================================
[docs]def get_per_page(request: HttpRequest) -> Optional[int]: """ Returns the number of items per page (a pagination preference) of the current user. Args: request: the :class:`django.http.request.HttpRequest` Returns: the number of items per page, or ``None`` if the user was not authenticated """ if not request.user.is_authenticated: return None # noinspection PyUnresolvedReferences profile = request.user.profile # type: UserProfile return profile.per_page
[docs]def get_patients_per_page(request: HttpRequest) -> Optional[int]: """ Returns the number of patients per page (a pagination preference, for the Patient Explorer view) of the current user. Args: request: the :class:`django.http.request.HttpRequest` Returns: the number of patients per page, or ``None`` if the user was not authenticated """ if not request.user.is_authenticated: return None # noinspection PyUnresolvedReferences profile = request.user.profile # type: UserProfile return profile.patients_per_page