Projectroles Django Settings

This document describes the Django settings for the projectroles app, which also control the configuration of other apps in a SODAR Core based site.

These settings are usually found in config/settings/*.py, with config/settings/base.py being the default configuration other files may override or extend.

If your site is based on sodar-django-site, mandatory settings are already set to their default values. In that case, you only need to modify or customize them where applicable.

If you are integrating django-sodar-core with an existing Django site or building your site from scratch without the recommended template, make sure to add all mandatory settings into your project.

For values retrieved from environment variables, make sure to configure your env accordingly. For development and testing, it is highly recommended to set DJANGO_READ_DOT_ENV_FILE=1 in your system’s environment variables and place the env variables into a .env file in the root directory of your Django site repository. See env.example for an example of such a file.

Site Package and Paths

The site package and path configuration should be found at the beginning of the default configuration file. Substitute {SITE_NAME} with the name of your site package.

import environ
SITE_PACKAGE = '{SITE_NAME}'
ROOT_DIR = environ.Path(__file__) - 3
APPS_DIR = ROOT_DIR.path(SITE_PACKAGE)

Apps

Apps installed from django-sodar-core are placed in THIRD_PARTY_APPS. The following apps need to be included in the list in order for SODAR Core to work:

THIRD_PARTY_APPS = [
    # ...
    'crispy_forms',
    'rules.apps.AutodiscoverRulesConfig',
    'djangoplugins',
    'pagedown',
    'markupfield',
    'rest_framework',
    'knox',
    'projectroles.apps.ProjectrolesConfig',
    'dal',
    'dal_select2',
    'dj_iconify.apps.DjIconifyConfig',
]

Database

Under DATABASES, the setting below is recommended:

DATABASES['default']['ATOMIC_REQUESTS'] = False

Note

If this conflicts with your existing set up, you can modify the code in your other apps to use e.g. @transaction.atomic.

Templates

Under TEMPLATES['OPTIONS']['context_processors'], add the required projectroles processors:

'projectroles.context_processors.urls_processor',
'projectroles.context_processors.site_app_processor',
'projectroles.context_processors.app_alerts_processor',
'projectroles.context_processors.sidebar_processor',

Email

Under EMAIL_CONFIGURATION or EMAIL, configure email settings:

EMAIL_SENDER = env('EMAIL_SENDER', default='noreply@example.com')
EMAIL_SUBJECT_PREFIX = env('EMAIL_SUBJECT_PREFIX', default='')

Authentication

AUTHENTICATION_BACKENDS should contain the following backend classes:

AUTHENTICATION_BACKENDS = [
    'rules.permissions.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
]

Note

The default setup by cookiecutter-django adds the allauth package. This can be left out of the project if not needed, as it mostly provides adapters for e.g. social media account logins. If removing allauth, you can also remove unused settings variables starting with ACCOUNT_*.

The following settings remain in your auth configuration:

AUTH_USER_MODEL = 'users.User'
LOGIN_REDIRECT_URL = 'home'
LOGIN_URL = 'login'

Icons

The ICONIFY_JSON_ROOT setting must point to the appropriate path within your static files directory in order to make icons work on your SODAR Core based site.

ICONIFY_JSON_ROOT = os.path.join(STATIC_ROOT, 'iconify')

Django REST Framework

To enable djangorestframework API views and knox authentication, these values should be added under DEFAULT_AUTHENTICATION_CLASSES:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'knox.auth.TokenAuthentication',
    ),
}

General Site Settings

For display in projectroles based templates, customize related variables to describe your site. SITE_INSTANCE_TITLE may be used to e.g. differentiate between site versions used for deployment or staging, use in different organizations, etc.

SITE_TITLE = 'Name of Your Project'
SITE_SUBTITLE = env.str('SITE_SUBTITLE', 'Beta')
SITE_INSTANCE_TITLE = env.str('SITE_INSTANCE_TITLE', 'Deployment Instance Name')

Projectroles Settings

Mandatory projectroles app settings are explained below:

PROJECTROLES_SITE_MODE

Site mode for remote project metadata synchronization, either SOURCE (allow others to read local projects) or TARGET (read projects from another site)

PROJECTROLES_TARGET_CREATE

Whether or not local projects can be created if site is in TARGET mode. If your site is in SOURCE mode, this setting has no effect.

PROJECTROLES_INVITE_EXPIRY_DAYS

Days until project email invites expire (int)

PROJECTROLES_SEND_EMAIL

Enable/disable email sending (bool)

PROJECTROLES_EMAIL_SENDER_REPLY

Whether replies are expected to the sender address (bool). If set False and nothing is set in the reply-to header, a “do not reply” note is added to the email body.

PROJECTROLES_ENABLE_SEARCH

Whether you want to enable SODAR search on your site (boolean)

PROJECTROLES_DEFAULT_ADMIN

User name of the default superuser account used in e.g. replacing an unavailable user or performing backend admin commands (string)

PROJECTROLES_TEMPLATE_INCLUDE_PATH

Full system path for custom template includes. The default path is {APPS_DIR}/templates/include (string)

Example:

# Projectroles app settings
PROJECTROLES_SITE_MODE = env.str('PROJECTROLES_SITE_MODE', 'TARGET')
PROJECTROLES_TARGET_CREATE = env.bool('PROJECTROLES_TARGET_CREATE', True)
PROJECTROLES_INVITE_EXPIRY_DAYS = env.int('PROJECTROLES_INVITE_EXPIRY_DAYS', 14)
PROJECTROLES_SEND_EMAIL = env.bool('PROJECTROLES_SEND_EMAIL', False)
PROJECTROLES_EMAIL_SENDER_REPLY = env.bool('PROJECTROLES_EMAIL_SENDER_REPLY', False)
PROJECTROLES_ENABLE_SEARCH = True
PROJECTROLES_DEFAULT_ADMIN = env.str('PROJECTROLES_DEFAULT_ADMIN', 'admin')

Optional Projectroles Settings

The following projectroles settings are optional:

PROJECTROLES_EMAIL_HEADER

Custom email header (string)

PROJECTROLES_EMAIL_FOOTER

Custom email footer (string)

PROJECTROLES_SECRET_LENGTH

Character length of secret token used in projectroles (int)

PROJECTROLES_SEARCH_PAGINATION

Amount of search results per each app to display on one page (int)

PROJECTROLES_HELP_HIGHLIGHT_DAYS

Days for highlighting tour help for new users (int)

PROJECTROLES_DISABLE_CATEGORIES

If set True, disable categories and only allow a list of projects on the root level (boolean) (see note)

PROJECTROLES_HIDE_PROJECT_APPS

Apps hidden from the project sidebar and dropdown menus for all users. The app views and URLs are still accessible via other links or knowing the URL. The names should correspond to the name property in project app plugins (list)

PROJECTROLES_HIDE_APP_LINKS

DEPRECATED, use PROJECTROLES_HIDE_PROJECT_APPS instead. This will be removed in v1.0

PROJECTROLES_DELEGATE_LIMIT

The number of delegate roles allowed per project. The amount is limited to 1 per project if not set, unlimited if set to 0. Will be ignored for remote projects synchronized from a source site (int)

PROJECTROLES_BROWSER_WARNING

If true, display a warning to users using Internet Explorer (bool)

PROJECTROLES_ALLOW_LOCAL_USERS

If true, roles for local non-LDAP users can be synchronized from a source during remote project sync if they exist on the target site. Similarly, local users will be selectable in member dropdowns when selecting users (bool)

PROJECTROLES_KIOSK_MODE

If true, allow accessing certain project views without user authentication in order to e.g. demonstrate features in a kiosk-style deployment. Also hides and/or disables views not intended to be used in this mode (bool)

PROJECTROLES_BREADCRUMB_STICKY

Set this false to make project breadcrumb navigation scroll along page content. If true, maintain a sticky breadcrumb below the titlebar instead. Assumed true if not set (bool)

PROJECTROLES_ALLOW_ANONYMOUS

If true, allow anonymous users to access the site and all projects where public_guest_access is set true (bool)

PROJECTROLES_SIDEBAR_ICON_SIZE

Set the icon size for the project sidebar. Minimum=18, maximum=42, default=36 (int)

PROJECTROLES_SEARCH_OMIT_APPS

List of apps to omit from search results (list)

PROJECTROLES_TARGET_SYNC_ENABLE

Enable/disable remote project synchronization as a target site. Ignored for source sites (bool)

PROJECTROLES_TARGET_SYNC_INTERVAL

Interval in minutes for remote project synchronization as a target site. Ignored for source sites (int)

Example:

# Projectroles app settings
# ...
PROJECTROLES_EMAIL_HEADER = 'This email has been sent by X from Y'
PROJECTROLES_EMAIL_FOOTER = 'For assistance contact admin@example.com'
PROJECTROLES_SECRET_LENGTH = 32
PROJECTROLES_SEARCH_PAGINATION = 5
PROJECTROLES_HELP_HIGHLIGHT_DAYS = 7
PROJECTROLES_DISABLE_CATEGORIES = True
PROJECTROLES_HIDE_PROJECT_APPS = ['filesfolders']
PROJECTROLES_DELEGATE_LIMIT = 1
PROJECTROLES_BROWSER_WARNING = True
PROJECTROLES_ALLOW_LOCAL_USERS = True
PROJECTROLES_KIOSK_MODE = False

Warning

Regarding PROJECTROLES_DISABLE_CATEGORIES: In the current SODAR core version remote site access and remote project synchronization are disabled if this option is used! Use only if a simple project list is specifically required in your site.

Warning

Regarding PROJECTROLES_ALLOW_LOCAL_USERS: Please note that this will allow synchronizing project roles to local non-LDAP users based on their user name. You should personally ensure that the users in question are authorized for these roles. Furthermore, only roles for existing local users will be synchronized. New local users will have to be added manually through the Django admin or shell on the target site.

Warning

The PROJECTROLES_KIOSK_MODE setting is under development and considered experimental. More implementation, testing and documentation is forthcoming.

Backend App Settings

The ENABLED_BACKEND_PLUGINS settings lists backend plugins implemented using BackendPluginPoint which are enabled in the configuration. For more information see Backend App Development.

ENABLED_BACKEND_PLUGINS = env.list('ENABLED_BACKEND_PLUGINS', None, [])

API View Settings (Optional)

If you want to build an API to your site using SODAR Core functionality, it is recommended to base your API views on projectroles.views.SODARAPIBaseView. Using this base class also allows you to define your API media type, version number and allowed versions via Django settings.

The recommended API setup uses accept header versioning. The SODAR_API_MEDIA_TYPE setting should be changed to your organization and API identification if API views are introduced. The SODAR_API_DEFAULT_HOST setting should post to the externally visible host of your server and be configured in your environment settings.

These settings are optional. Default values will be used if they are unset.

Example:

SODAR_API_DEFAULT_VERSION = '0.1'
SODAR_API_ACCEPTED_VERSIONS = [SODAR_API_DEFAULT_VERSION]
SODAR_API_MEDIA_TYPE = 'application/your.application+json'  # Change this
SODAR_API_DEFAULT_HOST = SODAR_API_DEFAULT_HOST = env.url('SODAR_API_DEFAULT_HOST', 'http://0.0.0.0:8000')

LDAP/AD Configuration (Optional)

If you want to utilize LDAP/AD user logins as configured by projectroles, you can add the following configuration. Make sure to also add the related env variables to your configuration.

This part of the setup is optional.

Note

In order to support LDAP, make sure you have installed the dependencies from utility/install_ldap_dependencies.sh and requirements/ldap.txt! For more information see Development Installation.

Note

If only using one LDAP/AD server, you can leave the “secondary LDAP server” values unset.

Hint

To help debug possible connection problems with your LDAP server(s), set LDAP_DEBUG=1 in your environment variables.

ENABLE_LDAP = env.bool('ENABLE_LDAP', False)
ENABLE_LDAP_SECONDARY = env.bool('ENABLE_LDAP_SECONDARY', False)
LDAP_DEBUG = env.bool('LDAP_DEBUG', False)

if ENABLE_LDAP:
    import itertools
    import ldap
    from django_auth_ldap.config import LDAPSearch

    if LDAP_DEBUG:
        ldap.set_option(ldap.OPT_DEBUG_LEVEL, 255)
    # Default values
    LDAP_DEFAULT_CONN_OPTIONS = {ldap.OPT_REFERRALS: 0}
    LDAP_DEFAULT_ATTR_MAP = {
        'first_name': 'givenName',
        'last_name': 'sn',
        'email': 'mail',
    }

    # Primary LDAP server
    AUTH_LDAP_SERVER_URI = env.str('AUTH_LDAP_SERVER_URI', None)
    AUTH_LDAP_BIND_DN = env.str('AUTH_LDAP_BIND_DN', None)
    AUTH_LDAP_BIND_PASSWORD = env.str('AUTH_LDAP_BIND_PASSWORD', None)
    AUTH_LDAP_START_TLS = env.str('AUTH_LDAP_START_TLS', False)
    AUTH_LDAP_CA_CERT_FILE = env.str('AUTH_LDAP_CA_CERT_FILE', None)
    AUTH_LDAP_CONNECTION_OPTIONS = {**LDAP_DEFAULT_CONN_OPTIONS}
    if AUTH_LDAP_CA_CERT_FILE is not None:
        AUTH_LDAP_CONNECTION_OPTIONS[
            ldap.OPT_X_TLS_CACERTFILE
        ] = AUTH_LDAP_CA_CERT_FILE
        AUTH_LDAP_CONNECTION_OPTIONS[ldap.OPT_X_TLS_NEWCTX] = 0
    AUTH_LDAP_USER_FILTER = env.str(
        'AUTH_LDAP_USER_FILTER', '(sAMAccountName=%(user)s)'
    )

    AUTH_LDAP_USER_SEARCH = LDAPSearch(
        env.str('AUTH_LDAP_USER_SEARCH_BASE', None),
        ldap.SCOPE_SUBTREE,
        AUTH_LDAP_USER_FILTER,
    )
    AUTH_LDAP_USER_ATTR_MAP = LDAP_DEFAULT_ATTR_MAP
    AUTH_LDAP_USERNAME_DOMAIN = env.str('AUTH_LDAP_USERNAME_DOMAIN', None)
    AUTH_LDAP_DOMAIN_PRINTABLE = env.str(
        'AUTH_LDAP_DOMAIN_PRINTABLE', AUTH_LDAP_USERNAME_DOMAIN
    )

    AUTHENTICATION_BACKENDS = tuple(
        itertools.chain(
            ('projectroles.auth_backends.PrimaryLDAPBackend',),
            AUTHENTICATION_BACKENDS,
        )
    )

    # Secondary LDAP server (optional)
    if ENABLE_LDAP_SECONDARY:
        AUTH_LDAP2_SERVER_URI = env.str('AUTH_LDAP2_SERVER_URI', None)
        AUTH_LDAP2_BIND_DN = env.str('AUTH_LDAP2_BIND_DN', None)
        AUTH_LDAP2_BIND_PASSWORD = env.str('AUTH_LDAP2_BIND_PASSWORD', None)
        AUTH_LDAP2_START_TLS = env.str('AUTH_LDAP2_START_TLS', False)
        AUTH_LDAP2_CA_CERT_FILE = env.str('AUTH_LDAP2_CA_CERT_FILE', None)
        AUTH_LDAP2_CONNECTION_OPTIONS = {**LDAP_DEFAULT_CONN_OPTIONS}
        if AUTH_LDAP2_CA_CERT_FILE is not None:
            AUTH_LDAP2_CONNECTION_OPTIONS[
                ldap.OPT_X_TLS_CACERTFILE
            ] = AUTH_LDAP2_CA_CERT_FILE
            AUTH_LDAP2_CONNECTION_OPTIONS[ldap.OPT_X_TLS_NEWCTX] = 0
        AUTH_LDAP2_USER_FILTER = env.str(
            'AUTH_LDAP2_USER_FILTER', '(sAMAccountName=%(user)s)'
        )

        AUTH_LDAP2_USER_SEARCH = LDAPSearch(
            env.str('AUTH_LDAP2_USER_SEARCH_BASE', None),
            ldap.SCOPE_SUBTREE,
            AUTH_LDAP2_USER_FILTER,
        )
        AUTH_LDAP2_USER_ATTR_MAP = LDAP_DEFAULT_ATTR_MAP
        AUTH_LDAP2_USERNAME_DOMAIN = env.str('AUTH_LDAP2_USERNAME_DOMAIN')
        AUTH_LDAP2_DOMAIN_PRINTABLE = env.str(
            'AUTH_LDAP2_DOMAIN_PRINTABLE', AUTH_LDAP2_USERNAME_DOMAIN
        )

        AUTHENTICATION_BACKENDS = tuple(
            itertools.chain(
                ('projectroles.auth_backends.SecondaryLDAPBackend',),
                AUTHENTICATION_BACKENDS,
            )
        )

SAML SSO Configuration (Optional)

Optional Single Sign-On (SSO) authorization via SAML is also available. To enable this feature, set ENABLE_SAML=1 in your environment. Configuring SAML for SSO requires proper configuration of the Keycloak SSO server and the SAML client library.

Keycloak

Create a new client in Keycloak and configure it as follows. Please note that Client ID can be chosen however you like, but it must match the setting in the client.

_images/keycloak_client_config.png

To generate the metadata.xml file required for the client, go to the Realm Settings page and in the General tab, click SAML 2.0 Identity Provider Metadata to download the xml data. Save it somewhere on the client, the preferred name is metadata.xml.

_images/keycloak_metadata_download.png

For the signing of the request send to the Keycloak server you will require a certificate and key provided by the Keycloak server and incorporated into the configuration of the client. Switch to the SAML Keys. Make sure to select PKCS12 as Archive Format.

_images/keycloak_saml_key_download1.png
_images/keycloak_saml_key_download2.png

Convert the archive on the commandline with the follow command and store them in some place on your client.

openssl pkcs12 -in keystore.p12 -password "pass:<PASSWORD>" -nodes | openssl x509 -out cert.pem
openssl pkcs12 -in keystore.p12 -password "pass:<PASSWORD>" -nodes -nocerts | openssl rsa -out key.pem

SODAR Core

Make sure that your config/settings/base.py contains the following configuration:

ENABLE_SAML = env.bool('ENABLE_SAML', False)
SAML2_AUTH = {
    # Required setting
    # Pysaml2 Saml client settings
    # See: https://pysaml2.readthedocs.io/en/latest/howto/config.html
    'SAML_CLIENT_SETTINGS': {
        # Optional entity ID string to be passed in the 'Issuer' element of
        # authn request, if required by the IDP.
        'entityid': env.str('SAML_CLIENT_ENTITY_ID', 'SODARcore'),
        'entitybaseurl': env.str(
            'SAML_CLIENT_ENTITY_URL', 'https://localhost:8000'
        ),
        # The auto(dynamic) metadata configuration URL of SAML2
        'metadata': {
            'local': [
                env.str('SAML_CLIENT_METADATA_FILE', 'metadata.xml'),
            ],
        },
        'service': {
            'sp': {
                'idp': env.str(
                    'SAML_CLIENT_IPD',
                    'https://sso.hpc.bihealth.org/auth/realms/cubi',
                ),
                # Keycloak expects client signature
                'authn_requests_signed': 'true',
                # Enforce POST binding which is required by keycloak
                'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
            },
        },
        'key_file': env.str('SAML_CLIENT_KEY_FILE', 'key.pem'),
        'cert_file': env.str('SAML_CLIENT_CERT_FILE', 'cert.pem'),
        'xmlsec_binary': env.str('SAML_CLIENT_XMLSEC1', '/usr/bin/xmlsec1'),
        'encryption_keypairs': [
            {
                'key_file': env.str('SAML_CLIENT_KEY_FILE', 'key.pem'),
                'cert_file': env.str('SAML_CLIENT_CERT_FILE', 'cert.pem'),
            }
        ],
    },
    # Custom target redirect URL after the user get logged in.
    # Defaults to /admin if not set. This setting will be overwritten if you
    # have parameter ?next= specified in the login URL.
    'DEFAULT_NEXT_URL': '/',
    # # Optional settings below
    # 'NEW_USER_PROFILE': {
    #     'USER_GROUPS': [],  # The default group name when a new user logs in
    #     'ACTIVE_STATUS': True,  # The default active status for new users
    #     'STAFF_STATUS': True,  # The staff status for new users
    #     'SUPERUSER_STATUS': False,  # The superuser status for new users
    # },
    # 'ATTRIBUTES_MAP': env.dict(
    #     'SAML_ATTRIBUTES_MAP',
    #     default={
    #         Change values to corresponding SAML2 userprofile attributes.
    #         'email': 'Email',
    #         'username': 'UserName',
    #         'first_name': 'FirstName',
    #         'last_name': 'LastName',
    #     }
    # ),
    # 'TRIGGER': {
    #     'FIND_USER': 'path.to.your.find.user.hook.method',
    #     'NEW_USER': 'path.to.your.new.user.hook.method',
    #     'CREATE_USER': 'path.to.your.create.user.hook.method',
    #     'BEFORE_LOGIN': 'path.to.your.login.hook.method',
    # },
    # Custom URL to validate incoming SAML requests against
    # 'ASSERTION_URL': 'https://your.url.here',
}

Add the following settings to your environment variables:

ENABLE_SAML=1
SAML_CLIENT_ENTITY_ID=<Entity ID configured in Keycloak>
SAML_CLIENT_ENTITY_URL=<Client URL, e.g. https://sodar-core.bihealth.org>
SAML_CLIENT_METADATA_FILE=<e.g. metadata.xml>
SAML_CLIENT_IPO=<SSO server URL, e.g. https://sso.hpc.bihealth.org/auth/realms/cubi>
SAML_CLIENT_KEY_FILE=<e.g. key.pem>
SAML_CLIENT_CERT_FILE=<e.g. cert.pem>
SAML_CLIENT_XMLSEC1=<e.g. /usr/bin/xmlsec1>

Global JS/CSS Include Modifications (Optional)

It is possible to supplement (or replace, see below) global Javascript and CSS includes of your SODAR Core site without altering the base template. You can place a list of custom includes into the list variables PROJECTROLES_CUSTOM_JS_INCLUDES and PROJECTROLES_CUSTOM_CSS_INCLUDES. These can either be local static file paths or web URLs to e.g. CDN served files.

If using the default CDN imports for JQuery, Bootstrap4 etc. are not an optimal solution in your use case due to e.g. network issues, you can disable these includes by setting PROJECTROLES_DISABLE_CDN_INCLUDES to True.

Warning

If disabling the default CDN includes, you must provide replacements for all disabled files in your custom includes. Otherwise your SODAR Core based site will not function correctly!

Example:

PROJECTROLES_DISABLE_CDN_INCLUDES = True
PROJECTROLES_CUSTOM_JS_INCLUDES = [
    STATIC_ROOT + '/your/path/jquery-3.3.1.min.js',
    STATIC_ROOT + '/your/path/popper.min.js',
    'https://some-cdn.com/bootstrap.min.js',
    # ...
]
PROJECTROLES_CUSTOM_CSS_INCLUDES = [
    STATIC_ROOT + '/your/path/bootstrap.min.css',
    # ...
]

It is also possible to define inline HTML in an environment variable and include it in the head tag of the base template. To use this feature, add HTML script as the value of the variable PROJECTROLES_INLINE_HEAD_INCLUDE.

Example:

PROJECTROLES_INLINE_HEAD_INCLUDE="<meta name=\"keywords\" content=\"SODAR Core\">"

Warning

Make sure you are inputting valid HTML or you risk breaking the HTML on all pages of your SODAR Core based site!

Modifying SODAR_CONSTANTS (Optional)

String identifiers used globally in SODAR project management are defined in the SODAR_CONSTANTS dictionary. It can be imported into your app code with the import:

from projectroles.models import SODAR_CONSTANTS

If you need to update or extend the constants for use your site, you can import the default dictionary into your Django settings and modify it as necessary with the following snippet:

from projectroles.constants import get_sodar_constants
SODAR_CONSTANTS = get_sodar_constants(default=True)
# Your changes here..

Warning

Modifying existing default constants may result in unwanted issues, especially on a site which already contains created projects. Proceed with caution!

Logging (Optional)

It is recommended to add “projectroles” under LOGGING['loggers']. For production, ERROR debug level is recommended.

The example site and SODAR Django Site template provide the LOGGING_APPS and LOGGING_FILE_PATH helpers for easily adding SODAR Core apps to logging and providing a system path for optional log file writing.

If you are using ManagementCommandLogger for logging your management command output, you can disable redundant console input in e.g. your test configuration by setting LOGGING_DISABLE_CMD_OUTPUT to True.