Metadata-Version: 2.1
Name: psu-base
Version: 0.2.1
Summary: Basic features used in all PSU Django apps
Home-page: https://github.com/PSU-OIT-ARC/django-psu-base
Author: Mike Gostomski
Author-email: mjg@pdx.edu
License: MIT License
Platform: UNKNOWN
Classifier: Framework :: Django :: 2.2
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.6
Description-Content-Type: text/markdown
Requires-Dist: certifi (>=2019.6.16)
Requires-Dist: chardet (>=3.0.4)
Requires-Dist: Django (>=2.2.2)
Requires-Dist: django-cas-ng (>=3.6.0)
Requires-Dist: idna (>=2.8)
Requires-Dist: lxml (>=4.3.4)
Requires-Dist: psycopg2-binary (>=2.8.3)
Requires-Dist: pylibmc (>=1.6.0)
Requires-Dist: python-cas (>=1.4.0)
Requires-Dist: python-memcached (>=1.59)
Requires-Dist: pytz (>=2019.1)
Requires-Dist: requests (>=2.22.0)
Requires-Dist: six (>=1.12.0)
Requires-Dist: sqlparse (>=0.3.0)
Requires-Dist: urllib3 (>=1.25.3)
Requires-Dist: django-crequest (>=2018.5.11)
Requires-Dist: arrow (>=0.14.5)

# PSU-Base

Django add-on for PSU apps which includes common utilities, such as:
-  PSU Single Sign-On (SSO)
-  Reference to static content server (e.g. for Bootstrap, FontAwesome4, and JQuery)
-  Displaying the name and version of the app

More documentation in [Confluence](https://portlandstate.atlassian.net/wiki/spaces/WDT/pages/713162905/Reusable+Django+Apps+The+Django+PSU+Plugin).

## Quick Start
### Dependencies
The following dependencies is REQUIRED and must be installed manually in your app:
- `django-cas-ng`
    ```sh
    pip install django-cas-ng
    ```
    Add to your app's `settings.py`
    ```python
    INSTALLED_APPS = [
        ...
        'django_cas_ng',
    ]
    ```

The following dependencies is REQUIRED in your system:
- `libpq-dev` (Ubuntu/Debian) to get the `pg_config` executable
    ```sh
    sudo apt install libpq-dev
    ```

### Installation
Inside your Django app (we recommend you use a virtual environment)
1. Set up your virtual envionment:
   ```bash
   cd my_django_project

   # Create a virtual environment directory 'env'
   python -m venv env
   ```

2. Until PSU-Base is hosted somewhere, for now you can install PSU-Base to your app as follows:

    1. Clone this repository
       ```sh
       git clone git@github.com:PSU-OIT-ARC/django-psu-base.git
       ```
    2. Build the thing
       ```sh
       cd django-psu-base
       python setup.py sdist bdist_wheel
       ```
       This will build two files under `django-psu-base/dist/`.
       -  `psu-base-0.1.tar.gz`
       -  `psu_base-0.1-py3-none-any.whl`

       Note that `0.1` will change according to the version.
    3. Copy either the `.tar.gz` or `.whl`file to your app
       ```sh
       cp dist/psu-base-0.1.tar.gz path/to/your/app
       ```
       OR
       ```sh
       cp dist/psu_base-0.1-py3-none-any.whl path/to/your/app
       ```
    4. `cd` into your app and install PSU-Base
       ```sh
       cd my_django_project
       source env/bin/activate # make sure virtual environment is active
       pip install psu-base-0.1.tar.gz
       ```
       OR
       ```sh
       pip install psu_base-0.1-py3-none-any.whl
       ```
       You get the idea...
3. (Once published to PyPi or PSU-hosted PyPi/Poetry (to be done soon)...) Install PSU-Base:
   ```bash
   # First, activate the virtual environment
   source env/bin/activate

   # with the environment activated...
   pip install django-psu-base
   ```

### Configuring Your App

1. Add PSU-Base to your INSTALLED_APPS in `settings.py`:
    ```python
    INSTALLED_APPS = [
       ...
       'psu_base',
    ]
    ```

2. Include the PSU-Base settings at the first or last line of your app's `app_settings.py`, `local_settings.py` or `settings.py`. For now, create the files `app_settings.py` for settings specific to your Django app, and `local_settings.py` for settings specific to the build environment in the __same directory__ as your `settings.py`.
    - Example for `app_settings.py`
        ```python
        # Get generic PSU settings
        from psu_base.psu_settings import *

        APP_VERSION = '0.0.1'

        # App identifiers
        APP_CODE = 'MY_APP'
        APP_NAME = 'My Special App'

        # On-premises apps will have additional "context" appended to the URL
        # i.e. https://app.banner.pdx.edu/<URL_CONTEXT>/index
        URL_CONTEXT = 'my_app'

        # CAS will return users to the root of the application
        CAS_REDIRECT_URL = f'/{URL_CONTEXT}'
        ```

    - Example for `local_settings.py`
        ```python
        # Environment choices: {DEV, TEST, PROD}
        ENVIRONMENT = 'DEV'

        # Name of machine running the application
        ALLOWED_HOSTS = ['localhost']

        # Debug mode (probably only true in DEV)
        DEBUG = True

        # SSO URL
        CAS_URL = 'https://sso-dev.oit.pdx.edu/idp/profile/cas/login'

        # Finti URL (dev, test, or prod)
        FINTI_URL = 'http://localhost:8888'
        ```

    Make sure to include `app_settings.py` and `local_settings.py` in your main `settings.py`:
    ```python
    ...

    # Add these lines at the end of the file

    # Get app-specific settings
    from .app_settings import *

    # Override settings with values for the local environment
    from .local_settings import *
    ```



3. Run `python manage.py migrate` to create the PSU-Base models

4. <a name="configureyourapp4"></a>Configure your app's top-level `urls.py` to include urls with CAS namespace
    ```python
    # my_app/urls.py
    from django.conf.urls import url
    from django.urls import path, include
    ...

    urlpatterns = [
        ...
        url(settings.URL_CONTEXT+'/base/', include(('psu_base.urls', 'psu_base'), namespace='base')),  # add this line

        # OR equivalently, ... either one works.
        # path('accounts/', include(('psu_base.urls', 'psu_base'), namespace='cas')),
    ]
    ```

### Using PSU-Base in Your App
#### Template
- Extend the `psu_base.html` for your base template. This will add a 'PSU' header and footer. The header will include links to login to PSU SSO. Use the block `pagecontent` to add your own content. E.g. in your app's `index.html`:
    ```jinja
    {% extends 'psu_base.html' %}

    {% block pagecontent %}
        <h1>Hello PSU Base!</h1>
    {% endblock %}
    ```
    ![Image of template rendered with psu base header and footer](images/index1.png)

#### Templatetags
-  Identity:
    ```jinja
    {% load identity_taglib %}

    {% display_name %}  <!-- displays user name when logged in -->
    ```

-  Static content:
    ```jinja
    {% load static_content_taglib %}

    <head>
        ...
        {% jquery %} <!-- import jquery -->
        {% bootstrap %} <!-- import bootstrap -->
        {% font_awesome version='4' %} <!-- import FontAwesome -->
    </head>
    ```

-  Utilities:
    ```jinja
    {% load utility_taglib %}

    {% app_name %} <!-- display app name -->
    {% app_version %} <!-- display app version -->
    ```

#### PSU-Base Classes

1. Log - provide logging interface ([Confluence docs](https://portlandstate.atlassian.net/wiki/spaces/WDT/pages/713719874/Logging))
    ```python
    ... # other imports
    from psu_base.classes.Log import Log

    log = Log()

    def method(param1, param2):
        log.trace([param1, param2])

        ... # do something

        try
            ...
        catch Exception as e:
            log.error(e)

        log.end(returned_value)  # Will log some metrics e.g. call duration if log.trace() was called first

   ...
   ```

2. IdentityCAS - Hold basic identity info expected to be returned from PSU's CAS/Shibboleth ([Confluence docs](https://portlandstate.atlassian.net/wiki/spaces/WDT/pages/713785376/CAS+Authentication))

    If PSU-Base urls were added to the app (see Configuring Your App step 4), you can view the information obtained by the Identity class. Test page URL: `base/test`

    ```python
    # psu-base/templatetags/identity_taglib.py
    from django import template
    from django.conf import settings
    ...
    from psu_base.classes.IdentityCAS import IdentityCAS

    register = template.Library()

    @register.simple_tag(takes_context=True)
    def display_name(context):
        if context['request'].user.is_authenticated:
            request = context['request']
            identity = IdentityCAS(request)
        return identity.full_name
    ```

3. Finti - helper/wrapper to handle JWT transactions in Finti API calls [Confluence docs](https://portlandstate.atlassian.net/wiki/spaces/WDT/pages/713949380/Calling+Finti+from+Django)

    __NOTE__: You must have `FINTI_URL` and `FINTI_TOKEN` configured for your app in `local_settings.py`.

    There are two methods available to make Finti calls:
    -  `jwt_get(self, request, path=None, parameters=None, include_metadata=False)`

        Use to call Finti APIs that use Javascript Web Tokens (JWT). When using this method, you must supply the JWT in the request META attribute: `request.META['HTTP_AUTHORIZATION']`

    -  `get(self, path, parameters=None, include_metadata=False)`

        Use to call Finti APIs that do not use JWT. You must supply a `FINTI_TOKEN` in the `local_settings.py` file.
        -  `path` : the API URI path without the Finti base URL i.e. `<scope>/<version>/<app_name>/[endpoint/args/querystrings]`

            For example: `wdt/v1/buildings/AB`.  Where: `wdt` is __scope__, `v1` is __version__, `buildings` is __app_name__, `AB` is an __endpoint__.


    -  Non-JWT Finti APIs have different response format; see: [Finti Docs](https://sites.google.com/a/pdx.edu/web-services/home/api). The `django-psu-base` Finti module wraps these reseponses into the format used by the Finti APIs which use JWT as shown below.

        The original response content is placed in the `'message'` attribute without modifications. For these kind of response content, the values for `'version'` and `'jwt'` are always `None`.  Responses from APIs that use JWT are returned as-is.

        Displays returned response with format:
        ```python
        {
            'status': ...,
            'result': ...,
            'version': ...,
            'jwt': ...,
            'message': ...
        }
        ```
        -  `status`: HTTP status code
        -  `result`: description of status code e.g. 200 = `'success'`
        -  `version`: API version
        -  `jwt`: JWT token (when available)
        -  `message`: information/data requested or error message

    If PSU-Base urls were added to the app (see [Configuring Your App step 4](#configureyourapp4)), you can test calling Finti APIs from the testing page at URL: `base/finti`

    ![Image of API test page](images/api_test.png)

