From e2fde667ba2159a82ac4ab5f78cb424403a7f953 Mon Sep 17 00:00:00 2001
From: Brandon Rodriguez <brodriguez8774@gmail.com>
Date: Fri, 28 Jun 2024 13:45:58 -0400
Subject: [PATCH] Copy "Django V4" files as baseline for "Django V5"

---
 django_v5/Pipfile                             |  49 ++
 django_v5/manage.py                           |  22 +
 django_v5/pytest.ini                          |  21 +
 django_v5/scripts/purge_migrations.sh         |  36 +
 django_v5/scripts/run_project.sh              |  71 ++
 django_v5/scripts/run_pytests.sh              |  68 ++
 django_v5/scripts/utils.sh                    | 722 ++++++++++++++++++
 django_v5/settings/__init__.py                |   0
 django_v5/settings/asgi.py                    |  16 +
 django_v5/settings/root_url.py                |  15 +
 django_v5/settings/settings.py                | 134 ++++
 django_v5/settings/urls.py                    |  33 +
 django_v5/settings/wsgi.py                    |  16 +
 django_v5/test_app/__init__.py                |   0
 django_v5/test_app/admin.py                   | 234 ++++++
 django_v5/test_app/apps.py                    |  11 +
 django_v5/test_app/forms.py                   |  35 +
 django_v5/test_app/management/__init__.py     |   0
 .../test_app/management/commands/__init__.py  |   0
 .../test_app/management/commands/seed.py      |  98 +++
 django_v5/test_app/migrations/__init__.py     |   0
 django_v5/test_app/models.py                  |  77 ++
 .../templates/registration/login.html         |  48 ++
 .../test_app/templates/test_app/api_send.html | 346 +++++++++
 .../test_app/templates/test_app/base.html     |  49 ++
 .../templates/test_app/group_check.html       |  16 +
 .../test_app/templates/test_app/index.html    |  55 ++
 .../templates/test_app/login_check.html       |  16 +
 .../templates/test_app/permission_check.html  |  16 +
 .../test_app/root_project_home_page.html      |  50 ++
 django_v5/test_app/tests/__init__.py          |   0
 .../test_app/tests/base_tests/__init__.py     |   0
 .../test_app/tests/base_tests/test_models.py  | 253 ++++++
 .../test_app/tests/base_tests/test_views.py   | 675 ++++++++++++++++
 .../test_app/tests/etc_tests/__init__.py      |   0
 .../test_app/tests/etc_tests/test_models.py   | 219 ++++++
 .../test_app/tests/etc_tests/test_views.py    | 613 +++++++++++++++
 django_v5/test_app/urls.py                    |  28 +
 django_v5/test_app/views.py                   | 559 ++++++++++++++
 39 files changed, 4601 insertions(+)
 create mode 100644 django_v5/Pipfile
 create mode 100755 django_v5/manage.py
 create mode 100644 django_v5/pytest.ini
 create mode 100755 django_v5/scripts/purge_migrations.sh
 create mode 100755 django_v5/scripts/run_project.sh
 create mode 100755 django_v5/scripts/run_pytests.sh
 create mode 100644 django_v5/scripts/utils.sh
 create mode 100644 django_v5/settings/__init__.py
 create mode 100644 django_v5/settings/asgi.py
 create mode 100644 django_v5/settings/root_url.py
 create mode 100644 django_v5/settings/settings.py
 create mode 100644 django_v5/settings/urls.py
 create mode 100644 django_v5/settings/wsgi.py
 create mode 100644 django_v5/test_app/__init__.py
 create mode 100644 django_v5/test_app/admin.py
 create mode 100644 django_v5/test_app/apps.py
 create mode 100644 django_v5/test_app/forms.py
 create mode 100644 django_v5/test_app/management/__init__.py
 create mode 100644 django_v5/test_app/management/commands/__init__.py
 create mode 100644 django_v5/test_app/management/commands/seed.py
 create mode 100644 django_v5/test_app/migrations/__init__.py
 create mode 100644 django_v5/test_app/models.py
 create mode 100644 django_v5/test_app/templates/registration/login.html
 create mode 100644 django_v5/test_app/templates/test_app/api_send.html
 create mode 100644 django_v5/test_app/templates/test_app/base.html
 create mode 100644 django_v5/test_app/templates/test_app/group_check.html
 create mode 100644 django_v5/test_app/templates/test_app/index.html
 create mode 100644 django_v5/test_app/templates/test_app/login_check.html
 create mode 100644 django_v5/test_app/templates/test_app/permission_check.html
 create mode 100644 django_v5/test_app/templates/test_app/root_project_home_page.html
 create mode 100644 django_v5/test_app/tests/__init__.py
 create mode 100644 django_v5/test_app/tests/base_tests/__init__.py
 create mode 100644 django_v5/test_app/tests/base_tests/test_models.py
 create mode 100644 django_v5/test_app/tests/base_tests/test_views.py
 create mode 100644 django_v5/test_app/tests/etc_tests/__init__.py
 create mode 100644 django_v5/test_app/tests/etc_tests/test_models.py
 create mode 100644 django_v5/test_app/tests/etc_tests/test_views.py
 create mode 100644 django_v5/test_app/urls.py
 create mode 100644 django_v5/test_app/views.py

diff --git a/django_v5/Pipfile b/django_v5/Pipfile
new file mode 100644
index 0000000..36e8f3a
--- /dev/null
+++ b/django_v5/Pipfile
@@ -0,0 +1,49 @@
+###
+ # Pipenv Package Declarations.
+ # This file is what Pipenv commands build off of.
+ ##
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+
+###
+ # Python version declaration.
+ ##
+[requires]
+python_version = "3.11"
+
+
+###
+ # General packages, installed via `pipenv sync`.
+ ##
+[packages]
+# General Django dependencies.
+django = "< 4.3.0"              # Core Django package, locked to latest 4.2 LTS.
+django-adminlte2-pdq = "*"      # Adds framework for easily styling site like adminlte2.
+django-localflavor = "*"        # Easy implementation of localization info, such as addresses.
+requests = "*"                  # Simple HTTP library. Useful for things like initiating API requests.
+
+###
+ # Development and testing packages, installed via `pipenv sync --dev`.
+ ##
+[dev-packages]
+# General dev dependencies.
+django-debug-toolbar = "*"      # Displays helpful debug-toolbar on side of browser window.
+django-dump-die = "*"           # Dump-and-die debugging tool.
+
+# Syntax-checking dependencies.
+autopep8 = "*"                  # Auto-formats files for pep8 recommendations. See `setup.cfg` for our exceptions.
+flake8 = "*"                    # Wrapper for autopep8 that allows extra configuration, etc.
+pylint = "*"                    # Linter for Python syntax. Must be run in console via "pylint" command.
+pylint-django = "*"             # Improves code analysis for Django projects.
+pylint-plugin-utils = "*"       # Additional pylint functionality for things like Django and Celery.
+
+# Testing/Pytest dependencies.
+coverage = "*"                  # Outputs testing coverage data.
+django-expanded-test-cases = "*"    # Utilities for easier testing.
+freezegun = "*"                 # Allows "freezing" tests to specific datetimes, for consistent checking and output.
+pytest = "*"                    # Base Pytest package. Current preferred testing method.
+pytest-django = "*"             # Additional Pytest logic for Django support.
+pytest-xdist = "*"              # Additional Pytest features, such as multithreading and looping.
diff --git a/django_v5/manage.py b/django_v5/manage.py
new file mode 100755
index 0000000..5767e97
--- /dev/null
+++ b/django_v5/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/django_v5/pytest.ini b/django_v5/pytest.ini
new file mode 100644
index 0000000..6a6e114
--- /dev/null
+++ b/django_v5/pytest.ini
@@ -0,0 +1,21 @@
+[pytest]
+DJANGO_SETTINGS_MODULE = settings.settings
+python_files = tests.py test_*.py
+log_level = NOTSET
+
+
+# Constructed as
+#   {action}:{message}:{category}:{module}:{lineno}
+#
+# For more details, see:
+#   https://docs.pytest.org/en/stable/how-to/capture-warnings.html
+#   https://docs.python.org/3/library/warnings.html#warning-filter
+#   https://stackoverflow.com/questions/57925071/how-do-i-avoid-getting-deprecationwarning-from-inside-dependencies-with-pytest
+filterwarnings =
+    ###
+     # Format is:
+     #   ignore:<regex_str_to_match>:<WarningType>
+     # Regex cannot have ":" character, it seems. Or else it thinks it's parsing the warning type.
+     # Warning must have full path defined if not a standard Python warning
+     # (aka django.utils.deprecation.RemovedInDjango50Warning).
+     ##
diff --git a/django_v5/scripts/purge_migrations.sh b/django_v5/scripts/purge_migrations.sh
new file mode 100755
index 0000000..34e6107
--- /dev/null
+++ b/django_v5/scripts/purge_migrations.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+###
+ # Script to purge all project migrations, for fresh database.
+ ##
+
+
+# Abort on error
+set -e
+
+
+# Import utility script.
+. $(dirname ${0})/utils.sh
+
+
+function main () {
+    # Change to project root.
+    cd ..
+
+    # Loop through all files in migration folder.
+    for file in "./test_app/migrations/"*
+    do
+        # Check if actually a file.
+        if [[ -f ${file} ]]
+        then
+            # Check that file follows migration name format.
+            if [[ "${file}" == *"/migrations/0"*".py" ]]
+            then
+                # Remove found migration file.
+                rm -f ${file}
+            fi
+        fi
+    done
+}
+
+
+main
diff --git a/django_v5/scripts/run_project.sh b/django_v5/scripts/run_project.sh
new file mode 100755
index 0000000..c56e97d
--- /dev/null
+++ b/django_v5/scripts/run_project.sh
@@ -0,0 +1,71 @@
+#!/usr/bin/env bash
+###
+ # Script to quickly setup and locally serve project in one command.
+ ##
+
+
+# Abort on error
+set -e
+
+
+# Import utility script.
+. $(dirname ${0})/utils.sh
+
+
+function main () {
+    # Change to project root.
+    cd ..
+
+    # Clear any current output in terminal.
+    clear
+
+    echo -e "${text_blue}Starting project setup for Django v4.2 test environment...${text_reset}"
+    echo ""
+
+    # Import corresponding virtual environment, if not already done.
+    source ./.venv/bin/activate
+
+    # Ensure latest pip and wheel are installed.
+    pip install --upgrade pip
+    pip install --upgrade wheel
+
+    # Ensure latest versions of all corresponding dependencies are installed.
+    # Because this project is assumed to only be used in local development,
+    # we only care about having a consistent Django major LTS version, and then
+    # otherwise always wanting the latest versions/bugfixes of all other
+    # corresponding packages used.
+    pipenv install --dev
+
+    # Clear out migration files if any are present.
+    ./scripts/purge_migrations.sh
+
+    # Reset local database if present.
+    if [[ -f "./db.sqlite3" ]]
+    then
+        rm -f "./db.sqlite3"
+    fi
+
+    # Make latest project migrations.
+    python manage.py makemigrations
+
+    # Run project migrations.
+    python manage.py migrate
+
+    # Generate project seeds.
+    python manage.py seed
+
+    echo ""
+    echo -e "${text_blue}Setup complete. Running project serve at ${text_orange}http://127.0.0.1:8041/${text_reset}"
+    echo ""
+    echo ""
+    echo ""
+    echo ""
+    echo ""
+
+    # Run project serve.
+    python manage.py runserver 8041 $@
+
+}
+
+
+main $@
diff --git a/django_v5/scripts/run_pytests.sh b/django_v5/scripts/run_pytests.sh
new file mode 100755
index 0000000..2124408
--- /dev/null
+++ b/django_v5/scripts/run_pytests.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+###
+ # Script to quickly setup local project and run pytests in one command.
+ ##
+
+
+# Abort on error
+set -e
+
+
+# Import utility script.
+. $(dirname ${0})/utils.sh
+
+
+function main () {
+    # Change to project root.
+    cd ..
+
+    # Clear any current output in terminal.
+    clear
+
+    echo -e "${text_blue}Starting project setup for Django v4.2 test environment...${text_reset}"
+    echo ""
+
+    # Import corresponding virtual environment, if not already done.
+    source ./.venv/bin/activate
+
+    # Ensure latest pip and wheel are installed.
+    pip install --upgrade pip
+    pip install --upgrade wheel
+
+    # Ensure latest versions of all corresponding dependencies are installed.
+    # Because this project is assumed to only be used in local development,
+    # we only care about having a consistent Django major LTS version, and then
+    # otherwise always wanting the latest versions/bugfixes of all other
+    # corresponding packages used.
+    pipenv install --dev
+
+    # Clear out migration files if any are present.
+    ./scripts/purge_migrations.sh
+
+    # Reset local database if present.
+    if [[ -f "./db.sqlite3" ]]
+    then
+        rm -f "./db.sqlite3"
+    fi
+
+    # Make latest project migrations.
+    python manage.py makemigrations
+
+    # Run project migrations.
+    python manage.py migrate
+
+    echo ""
+    echo -e "${text_blue}Setup complete. Running project pytests.${text_reset}"
+    echo ""
+    echo ""
+    echo ""
+    echo ""
+    echo ""
+
+    # Run project serve.
+    pytest $@
+
+}
+
+
+main $@
diff --git a/django_v5/scripts/utils.sh b/django_v5/scripts/utils.sh
new file mode 100644
index 0000000..4e2b7dc
--- /dev/null
+++ b/django_v5/scripts/utils.sh
@@ -0,0 +1,722 @@
+#!/usr/bin/env bash
+###
+ # A helper/utility script to be imported by other scripts.
+ #
+ # Intended to hold very common functionality (that's surprisingly not built into bash) such as string upper/lower,
+ # and checking current user value, and prompts of yes/no user input.
+ #
+ # https://git.brandon-rodriguez.com/scripts/bash/utils
+ # Version 1.3.1
+ ##
+
+
+#region Global Utility Variables.
+
+# Color Output Variables.
+text_reset="\033[0m"
+text_black="\033[0;30m"
+text_red="\033[0;31m"
+text_green="\033[0;32m"
+text_orange="\033[0;33m"
+text_blue="\033[0;34m"
+text_purple="\033[0;35m"
+text_cyan="\033[1;36m"
+text_yellow="\033[1;33m"
+text_white="\033[1;37m"
+
+# Arg, Kwarg, and Flag Return Variables.
+args=()
+flags=()
+declare -A kwargs=()
+global_args=()
+global_flags=()
+declare -A global_kwargs=()
+help_flags=false
+
+# Function Return Variables.
+return_value=""
+file_name=""
+file_extension=""
+
+#endregion Global Utility Variables
+
+
+#region Script Setup Functions
+
+###
+ # Normalizes the location of the terminal to directory of utils.sh file.
+ #
+ # The point is so that the execution of a script will handle the same, regardless of terminal directory location at
+ # script start. Ex: User can start script at either their home folder, or project root, (or any other directory) and
+ # relative directory handling will still function the same in either case.
+ #
+ # Automatically called on script import.
+ #
+ # Return Variable(s): return_value
+ ##
+function normalize_terminal () {
+    cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+}
+
+
+###
+ # Handles passed values.
+ #
+ # Splits into "args", "kwargs", and "flags":
+ #  * Args are any other values that don't match below two formats.
+ #      * Ex: "True", "5", and "test".
+ #  * Kwargs are key-value pairs, where the key comes first and starts with "--" or "-".
+ #      * Note that the associated value must come immediately after the key.
+ #      * Ex: "--type int", "--dir ./test", and "--name Bob".
+ #  * Flags are any variable that starts with "-" or "--", and must be defined by the calling script.
+ #      * To define flags, populate a "possible_flags" array with flag values prior to calling util script.
+ #      * These are effectively treated as booleans. True if present, false otherwise.
+ #      * Ex: "-a", "--b", "-run", and "--test".
+
+ #
+ # Ordering of provided args, kwargs, and flags should not matter, aside from kwarg keys needing a corresponding value
+ # as the direct next processed value.
+ #
+ # Note that "-h" and "--help" are handled separately, and considered to both be "help flags".
+ # If either one is provided, then a "help_flags" global variable is set to true.
+ # The expectation is that some kind of "display help" function will run instead of normal script functionality.
+ #
+ # Return Variable(s): help_flags, args, kwargs, flags, global_args, global_kwargs, and global_flags
+ ##
+handle_args_kwargs () {
+    local handle_kwarg=false
+    local kwarg_key=""
+    set_global_args=false
+    set_global_flags=false
+    set_global_kwargs=false
+
+    # On first run, set "global" arg/kwarg values, as backup.
+    # Useful in case any functions ever call this for additional arg/kwarg/flag handling.
+    # Prevents original passed values from being overriden by a function's passed values.
+    if [[ ${#global_args[@]} == 0 && ${#global_flags[@]} == 0 && ${#global_kwargs[@]} == 0 ]]
+    then
+        set_global_args=true
+        set_global_flags=true
+        set_global_kwargs=true
+    else
+        args=()
+        flags=()
+        kwargs=()
+    fi
+
+    # Parse all args.
+    for arg in ${@}
+    do
+        # Check arg type, based on substring match.
+        if [[ "${arg}" == "-h" || "${arg}" == "--help" ]]
+        then
+            # Special handling for help flags.
+            help_flags=true
+
+        # Handle for flags.
+        elif [[ ${#possible_flags[@]} > 0 && ${possible_flags[@]} =~ "${arg}" ]]
+        then
+            # Check for kwarg handling bool.
+            if [[ ${handle_kwarg} == true ]]
+            then
+                # Expected arg to fill value for key-value pair. Got flag. Raise error.
+                echo -e "${text_red}Expected value for kwarg key of \"${kwarg_key}\". Got a flag of \"${arg}\" instead.${text_reset}"
+                exit 1
+            else
+                # Save kwarg key and process next value.
+                if [[ "${arg}" == "--"* ]]
+                then
+                    # Handle for double dash flag.
+                    local new_flag=${arg#--}
+                elif [[ "${arg}" == "-"* ]]
+                then
+                    # Handle for single dash flag.
+                    local new_flag=${arg#-}
+                else
+                    # Unexpected kwarg value.
+                    echo -e "${text_red}Unexpected flag type of \"${arg}\". Unable to proceed.${text_reset}"
+                    exit 1
+                fi
+
+                flags+=( ${new_flag} )
+
+                # Optionally populate global flags.
+                if [[ ${set_global_flags} == true ]]
+                then
+                    global_flags+=( ${new_flag} )
+                fi
+            fi
+
+        # Handle for kwargs.
+        elif [[ ("${arg}" == "-"* || "${arg}" == "--"*) && "${arg}" != "---"*  ]]
+        then
+            # Check for kwarg handling bool.
+            if [[ ${handle_kwarg} == true ]]
+            then
+                # Expected arg to fill value for key-value pair. Got kwarg key. Raise error.
+                echo -e "${text_red}Expected value for kwarg key of \"${kwarg_key}\". Got another key of \"${arg}\" instead.${text_reset}"
+                exit 1
+            else
+                # Save kwarg key and process next value.
+                if [[ "${arg}" == "--"* ]]
+                then
+                    # Handle for two dash kwarg.
+                    kwarg_key=${arg#--}
+                    handle_kwarg=true
+                elif [[ "${arg}" == "-"* ]]
+                then
+                    # Handle for one dash kwarg.
+                    kwarg_key=${arg#-}
+                    handle_kwarg=true
+                else
+                    # Unexpected kwarg value.
+                    echo -e "${text_red}Unexpected kwarg key type of \"${arg}\". Unable to proceed.${text_reset}"
+                    exit 1
+                fi
+            fi
+
+        # Handle for args.
+        else
+            # Check for kwarg handling bool.
+            if [[ ${handle_kwarg} == true ]]
+            then
+                # Set key-value kwarg pair.
+                kwargs[${kwarg_key}]=${arg}
+                handle_kwarg=false
+
+                # Optionally populate global kwargs.
+                if [[ ${set_global_kwargs} == true ]]
+                then
+                    global_kwargs[${kwarg_key}]=${arg}
+                fi
+
+            else
+                # Add arg to list of args.
+                args+=( ${arg} )
+
+                # Optionally populate global args.
+                if [[ ${set_global_args} == true ]]
+                then
+                    global_args+=( ${arg} )
+                fi
+            fi
+        fi
+
+    done
+}
+
+#endregion Script Setup Functions
+
+
+#region Directory Functions
+
+###
+ # Gets absolute path of passed location.
+ #
+ # Return Variable(s): return_value
+ ##
+function get_absolute_path () {
+
+    # Check number of passed function args.
+    if [[ ${#} == 1 ]]
+    then
+        # Expected number of args passed. Continue.
+
+        # Check location type.
+        if [[ -f ${1} ]]
+        then
+            # Handle for file.
+            return_value="$(cd "$(dirname "${1}")"; pwd -P)/$(basename "${1}")"
+
+        elif  [[ -d ${1} ]]
+        then
+            # Handle for directory.
+
+            # Extra logic to properly handle values of "./" and "../".
+            local current_dir="$(pwd -P)"
+            cd ${1}
+
+            # Then call this to have consistent symlink handling as files.
+            return_value="$(cd "$(dirname "$(pwd -P)")"; pwd -P)/$(basename "$(pwd -P)")"
+
+            # Change back to original location once value is set.
+            cd ${current_dir}
+        else
+            echo -e "${text_red}Passed value ( ${1} ) is not a valid file or directory.${text_reset}"
+            exit 1
+        fi
+
+    # Handle for too many args.
+    elif [[ ${#} > 1 ]]
+    then
+        echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}"
+        exit 1
+
+    # Handle for too few args.
+    else
+        echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}"
+        exit 1
+    fi
+}
+
+
+###
+ # If no arg is provided, returns full path of current directory.
+ # If arg is passed, then returns absolute path if was directory, or full path of parent directory if was file.
+ #
+ # Return Variable(s): return_value
+ ##
+function get_directory_path () {
+
+    # Check number of passed function args.
+    if [[ ${#} == 0 ]]
+    then
+        # No args passed. Handle for current directory.
+        get_absolute_path ./
+
+    # Handle for one arg.
+    elif [[ ${#} == 1 ]]
+    then
+
+        # Check location type.
+        if [[ -f ${1} ]]
+        then
+            # Handle for file.
+            get_absolute_path ${1}
+            return_value=${return_value%/*}
+
+        elif [[ -d ${1} ]]
+        then
+            # Handle for directory.
+            get_absolute_path ${1}
+
+        else
+            echo -e "${text_red}Passed value ( ${1} ) is not a valid file or directory.${text_reset}"
+            exit 1
+        fi
+
+    # Handle for too many args.
+    else
+        echo -e "${text_red}Too many args passed. Expected zero or one args, received ${#}.${text_reset}"
+        exit 1
+    fi
+}
+
+
+###
+ # If no arg is provided, then returns name of current directory.
+ # If arg is passed, then returns name of directory, or parent directory of file.
+ #
+ # Return Variable(s): return_value
+ ##
+function get_directory_name () {
+
+    # Check number of passed function args.
+    if [[ ${#} == 0 ]]
+    then
+        # No args passed. Pass current directory to parent function.
+        get_directory_path ./
+        return_value=${return_value##*/}
+
+    # Handle for one arg.
+    elif [[ ${#} == 1 ]]
+    then
+
+        # Pass provided value to parent function.
+        get_directory_path ${1}
+        return_value=${return_value##*/}
+
+    # Handle for too many args.
+    else
+        echo -e "${text_red}Too many args passed. Expected zero or one args, received ${#}.${text_reset}"
+        exit 1
+    fi
+}
+
+
+###
+ # Parses passed file, getting base file name and file extension.
+ #
+ # Return Variable(s): return_value
+ ##
+function parse_file_name () {
+
+    # Check number of passed function args.
+    if [[ ${#} == 1 ]]
+    then
+        # Expected number of args passed. Continue.
+        if [[ -f ${1} ]]
+        then
+            # Handle for file.
+            get_absolute_path ${1}
+            return_value=${return_value##*/}
+
+            file_name=""
+            file_extension=""
+            _recurse_file_extension ${return_value}
+        else
+            echo -e "${text_red}Passed value ( ${1} ) is not a valid file.${text_reset}"
+            exit 1
+        fi
+
+    # Handle for too many args.
+    elif [[ ${#} > 1 ]]
+    then
+        echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}"
+        exit 1
+
+    # Handle for too few args.
+    else
+        echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}"
+        exit 1
+    fi
+}
+
+
+###
+ # Recursive helper function for parse_file_name().
+ # Determines base file name and full file extension.
+ #
+ # Return Variable(s): file_name and file_extension
+ ##
+function _recurse_file_extension () {
+    local passed_value=${1}
+    local parsed_extension=${passed_value##*.}
+
+    # Check if file extension was found. Occurs when variables are not identical.
+    if [[ ${parsed_extension} != ${passed_value} ]]
+    then
+        # Extension found. Iterate once more.
+        file_name=${passed_value%.${parsed_extension}}
+
+        # Handle if global var is currently empty or not.
+        if [[ ${file_extension} == "" ]]
+        then
+            file_extension=".${parsed_extension}"
+        else
+            file_extension=".${parsed_extension}${file_extension}"
+        fi
+
+        # Call recursive function once more.
+        _recurse_file_extension ${file_name}
+    fi
+}
+
+
+#endregion Directory Functions
+
+
+#region User Check Functions
+
+###
+ # Checks if current username matches passed value.
+ # In particular, is used to check if user is sudo/root user.
+ #
+ # Return Variable(s): None
+ ##
+function check_is_user () {
+
+    # Check number of passed function args.
+    if [[ ${#} == 1 ]]
+    then
+        # Expected number of args passed. Continue.
+        local username=$USER
+        local check_user=$1
+
+        # Determine user to check for.
+        to_lower ${check_user}
+        if [[ "${return_value}" == "root" || "${return_value}" == "sudo" ]]
+        then
+            # Special logic for checking if "sudo"/"root" user.
+            if [[ "$EUID" != 0 ]]
+            then
+                echo -e "${text_red}Sudo permissions required. Please run as root.${text_reset}"
+                exit
+            fi
+        else
+            # Standard logic for all other user checks.
+            if [[ "${username}" != "${check_user}" ]]
+            then
+                echo -e "${text_red}User check (${check_user}) failed. Current user is ${username}.${text_reset}"
+                exit 1
+            fi
+        fi
+
+    # Handle for too many args.
+    elif [[ ${#} > 1 ]]
+    then
+        echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}"
+        exit 1
+
+    # Handle for too few args.
+    else
+        echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}"
+        exit 1
+    fi
+}
+
+
+###
+ # Checks if current username does not match passed value.
+ # In particular, is used to check if user is not sudo/root user.
+ #
+ # Return Variable(s): None
+ ##
+function check_is_not_user () {
+
+    # Check number of passed function args.
+    if [[ ${#} == 1 ]]
+    then
+        # Expected number of args passed. Continue.
+        local username=$USER
+        local check_user=$1
+
+        # Determine user to check for.
+        to_lower ${check_user}
+        if [[ "${return_value}" == "root" || "${return_value}" == "sudo" ]]
+        then
+            # Special logic for checking if "sudo"/"root" user.
+            if [[ "$EUID" == 0 ]]
+            then
+                echo -e "${text_red}Standard permissions required. Please run as non-root user.${text_reset}"
+                exit
+            fi
+        else
+            # Standard logic for all other user checks.
+            if [[ "${username}" == "${check_user}" ]]
+            then
+                echo -e "${text_red}Not-user check (${check_user}) failed. Current user is ${username}.${text_reset}"
+                exit 1
+            fi
+        fi
+
+    # Handle for too many args.
+    elif [[ ${#} > 1 ]]
+    then
+        echo -e "${text_red}Too many args passed. Expected one arg, received ${#}.${text_reset}"
+        exit 1
+
+    # Handle for too few args.
+    else
+        echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}"
+        exit 1
+    fi
+}
+
+#endregion User Check Functions
+
+
+#region Text Manipulation Functions
+
+###
+ # Converts one or more passed args to uppercase characters.
+ #
+ # Return Variable(s): return_value
+ ##
+function to_upper () {
+
+    # Check number of passed function args.
+    if [[ ${#} > 0 ]]
+    then
+        # At least one arg passed. Loop through each one.
+        return_value=()
+        for arg in ${@}
+        do
+            return_value+=( $(echo "${arg}" | tr '[:lower:]' '[:upper:]') )
+        done
+
+    # No args passed.
+    else
+        echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}"
+        exit 1
+    fi
+}
+
+
+###
+ # Convers one or more passed args to lowercase characters.
+ #
+ # Return Variable(s): return_value
+ ##
+function to_lower () {
+
+    # Check number of passed function args.
+    if [[ ${#} > 0 ]]
+    then
+        # At least one arg passed. Loop through each one.
+        return_value=()
+        for arg in ${@}
+        do
+            return_value+=( $(echo "${arg}" | tr '[:upper:]' '[:lower:]') )
+        done
+
+    # No args passed.
+    else
+        echo -e "${text_red}Too few args passed. Expected one arg, received 0.${text_reset}"
+        exit 1
+    fi
+}
+
+#endregion Text Manipulation Functions
+
+
+#region Display Functions
+
+###
+ # Prompts user for yes/no confirmation. Returns True if yes.
+ # Accepts "yes", "ye", and "y". Input values are treated as case insensitive.
+ # All other values are treated as "no".
+ #
+ # Return Variable(s): return_value
+ ##
+function get_user_confirmation () {
+    # Check number of passed function args.
+    if [[ ${#} == 0 ]]
+    then
+        # Handle prompt for no args passed.
+        echo -e "[${text_cyan}Yes${text_reset}/${text_cyan}No${text_reset}]"
+
+    elif [[ ${#} == 1 ]]
+    then
+        # Handle prompt for one arg passed.
+        echo -e "${1} [${text_cyan}Yes${text_reset}/${text_cyan}No${text_reset}]"
+
+    else
+        # Handle for too many args.
+        echo -e "${text_red}Too many args passed. Expected zero or one arg, received ${#}.${text_reset}"
+        exit 1
+    fi
+
+    # Get user input.
+    read -p " " return_value
+    echo ""
+
+    # Convert input to lower for easy parsing.
+    to_lower "${return_value}"
+
+    # Finally parse user input.
+    if [[ "${return_value}" == "y" || "${return_value}" == "ye" || "${return_value}" == "yes" ]]
+    then
+        # User provided "yes". Return True.
+        return_value=true
+    else
+        # For all other values, return False.
+        return_value=false
+    fi
+}
+
+
+###
+ # Prints out all available text colors provided by this script.
+ #
+ # Return Variable(s): None
+ ##
+function display_text_colors () {
+    echo ""
+    echo "Displaying all available text colors:"
+    echo -e "   ${text_black}Black${text_reset}"
+    echo -e "   ${text_red}Red${text_reset}"
+    echo -e "   ${text_green}Green${text_reset}"
+    echo -e "   ${text_orange}Orange${text_reset}"
+    echo -e "   ${text_blue}Blue${text_reset}"
+    echo -e "   ${text_purple}Purple${text_reset}"
+    echo -e "   ${text_cyan}Cyan${text_reset}"
+    echo -e "   ${text_yellow}Yellow${text_reset}"
+    echo -e "   ${text_white}White${text_reset}"
+    echo ""
+}
+
+
+###
+ # Prints out all current flags, args, and kwargs, as determined by handle_args_kwargs() function.
+ # If function has been called multiple times, then will also print out global values.
+ #
+ # Return Variable(s): None
+ ##
+function display_args_kwargs () {
+    echo ""
+    echo -e "${text_blue}Displaying processed flags, args, and kwargs.${text_reset}"
+    echo ""
+
+    # Display for flags.
+    if [[ ${#flags[@]} > 0: ]]
+    then
+        echo -e " ${text_purple}Flags${text_reset} (${#flags[@]} Total):"
+        echo "    ${flags[@]}"
+    else
+        echo " No Flags Set."
+    fi
+
+    # Display for args.
+    if [[ ${#args[@]} > 0 ]]
+    then
+        echo -e " ${text_purple}Args${text_reset} (${#args[@]} Total):"
+        for index in ${!args[@]}
+        do
+            echo "    ${index}: ${args[${index}]}"
+        done
+    else
+        echo " No Args Set."
+    fi
+
+    # Display for kwargs.
+    if [[ ${#kwargs[@]} > 0 ]]
+    then
+        echo -e " ${text_purple}Kwargs${text_reset} (${#kwargs[@]} Total):"
+        for key in ${!kwargs[@]}
+        do
+            echo "    ${key}: ${kwargs[${key}]}"
+        done
+    else
+        echo " No Kwargs Set."
+    fi
+
+    # Only display if global values are different.
+    if [[ ${flags[@]} != ${global_flags[@]} || ${args[@]} != ${global_args[@]} || ${kwargs[@]} != ${global_kwargs[@]} ]]
+    then
+        echo ""
+        echo ""
+
+        # Display for global flags.
+        if [[ ${#global_flags[@]} > 0: ]]
+        then
+            echo -e " ${text_purple}Global Flags${text_reset} (${#global_flags[@]} Total):"
+            echo "    ${global_flags[@]}"
+        else
+            echo " No Global Flags Set."
+        fi
+
+        # Display for global args.
+        if [[ ${#global_args[@]} > 0 ]]
+        then
+            echo -e " ${text_purple}Global Args${text_reset} (${#global_args[@]} Total):"
+            for index in ${!global_args[@]}
+            do
+                echo "    ${index}: ${global_args[${index}]}"
+            done
+        else
+            echo " No Global Args Set."
+        fi
+
+        # Display for global kwargs.
+        if [[ ${#global_kwargs[@]} > 0 ]]
+        then
+            echo -e " ${text_purple}Global Kwargs${text_reset} (${#global_kwargs[@]} Total):"
+            for key in ${!global_kwargs[@]}
+            do
+                echo "    ${key}: ${global_kwargs[${key}]}"
+            done
+        else
+            echo " No Global Kwargs Set."
+        fi
+    fi
+
+    echo ""
+}
+
+#endregion Display Functions
+
+
+# Functions to call on script import.
+normalize_terminal
+handle_args_kwargs ${@}
diff --git a/django_v5/settings/__init__.py b/django_v5/settings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/settings/asgi.py b/django_v5/settings/asgi.py
new file mode 100644
index 0000000..684f9cc
--- /dev/null
+++ b/django_v5/settings/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for Django v4.2 test project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.settings')
+
+application = get_asgi_application()
diff --git a/django_v5/settings/root_url.py b/django_v5/settings/root_url.py
new file mode 100644
index 0000000..1799e94
--- /dev/null
+++ b/django_v5/settings/root_url.py
@@ -0,0 +1,15 @@
+"""
+Root Url for Django v4.2 test project.
+"""
+
+# Third-Party Imports.
+from django.urls import path
+
+# Internal Imports.
+from test_app.views import root_project_home_page
+
+
+app_name = 'root_project_home_page'
+urlpatterns = [
+    path('', root_project_home_page, name='index')
+]
diff --git a/django_v5/settings/settings.py b/django_v5/settings/settings.py
new file mode 100644
index 0000000..e50e2c2
--- /dev/null
+++ b/django_v5/settings/settings.py
@@ -0,0 +1,134 @@
+"""
+Django settings for Django v4.2 test project.
+"""
+
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'django-insecure-1vch$yfewx8bsr-@9y@7r)&7vpieg^7x7m#jq%9)^1b9$+^)97'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    # Project apps.
+    'test_app.apps.TestAppConfig',
+
+    # AdminLTE2 Package.
+    'adminlte2_pdq',
+
+    # DjangoDD Package.
+    'django_dump_die',
+
+    # DjangoETC Package.
+    'django_expanded_test_cases',
+
+    # Built-in Django apps.
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+]
+
+MIDDLEWARE = [
+    # Package middleware.
+    'django_dump_die.middleware.DumpAndDieMiddleware',
+
+    # Built-in Django middleware.
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'settings.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'settings.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': str(BASE_DIR.joinpath('db.sqlite3')),
+    }
+}
+
+
+AUTH_USER_MODEL = 'test_app.User'
+
+# Password validation
+# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/4.2/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/4.2/howto/static-files/
+
+STATIC_URL = 'static/'
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
diff --git a/django_v5/settings/urls.py b/django_v5/settings/urls.py
new file mode 100644
index 0000000..3f6b504
--- /dev/null
+++ b/django_v5/settings/urls.py
@@ -0,0 +1,33 @@
+"""
+Django v4.2 test project URL Configuration
+"""
+
+from django.contrib import admin
+from django.urls import include, path
+
+
+urlpatterns = [
+    # Django Admin views.
+    path('admin/', admin.site.urls),
+
+    # Django authentication views.
+    path('accounts/', include('django.contrib.auth.urls')),
+
+    # Basic/minimalistic Django application.
+    path('test_app/', include('test_app.urls')),
+
+    # Package testing views.
+
+    # AdminLTE2 routes for demo purposes.
+    path('adminlte2/', include('adminlte2_pdq.urls')),
+
+    # DjangoDD routes for demo purposes.
+    path('dd/', include('django_dump_die.urls')),
+    path('dd_tests/', include('django_dump_die.test_urls')),
+
+    # DjangoETC routes for demo purposes.
+    path('django_etc/', include('django_expanded_test_cases.test_urls')),
+
+    # Default project root view.
+    path('', include('settings.root_url')),
+]
diff --git a/django_v5/settings/wsgi.py b/django_v5/settings/wsgi.py
new file mode 100644
index 0000000..247d834
--- /dev/null
+++ b/django_v5/settings/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for Django v4.2 test project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings.settings')
+
+application = get_wsgi_application()
diff --git a/django_v5/test_app/__init__.py b/django_v5/test_app/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/test_app/admin.py b/django_v5/test_app/admin.py
new file mode 100644
index 0000000..4be79cb
--- /dev/null
+++ b/django_v5/test_app/admin.py
@@ -0,0 +1,234 @@
+"""
+Admin views for Django v4.2 test project app.
+"""
+
+# Third-Party Imports.
+from django.conf import settings
+from django.contrib import admin
+from django.contrib.admin.models import LogEntry
+from django.contrib.auth import get_user_model
+from django.contrib.auth.admin import GroupAdmin, UserAdmin
+from django.contrib.auth.models import Group, Permission
+from django.contrib.sessions.models import Session
+
+
+# region Admin Inlines
+
+class GroupUserInline(admin.TabularInline):
+    model = Group.user_set.through
+    extra = 0
+
+
+class PermissionGroupInline(admin.TabularInline):
+    model = Permission.group_set.through
+    extra = 0
+
+
+class PermissionUserInline(admin.TabularInline):
+    model = Permission.user_set.through
+    extra = 0
+
+# endregion Admin Inlines
+
+
+# region Admin Definitions
+
+class DjangoLogAdmin(admin.ModelAdmin):
+    """
+    Admin handling for Django's built-in admin view "LogEntry" models.
+    """
+    # Fields to display in admin list view.
+    list_display = ('user', 'content_type', 'object_repr', 'action_time')
+    if settings.DEBUG:
+        list_display = ('id',) + list_display
+
+    # Default field ordering in admin list view.
+    ordering = ('-action_time', 'user__username', 'content_type')
+
+    # Date filtering in admin list view.
+    date_hierarchy = 'action_time'
+
+    # Read only fields for admin detail view.
+    readonly_fields = ('id', 'action_time')
+
+    # Fieldset organization for admin detail view.
+    fieldsets = (
+        (None, {
+            'fields': (
+                'user',
+                'content_type__app_label',
+                'content_type__model',
+                'object_id',
+                'object_repr',
+                'action_flag',
+                'change_message',
+            )
+        }),
+        ('Advanced', {
+            'classes': ('collapse',),
+            'fields': ('id', 'action_time')
+        }),
+    )
+
+
+class DjangoUserAdmin(UserAdmin):
+    """
+    Admin handling for Django's built-in authentication "User" models.
+    """
+    # Fields to display in admin list view.
+    list_display = ('username', 'first_name', 'last_name', 'email', 'is_active', 'last_login')
+    if settings.DEBUG:
+        list_display = ('id',) + list_display
+
+    # Default field ordering in admin list view.
+    ordering = ('username',)
+
+    # Fields to filter by in admin list view.
+    list_filter = ('is_active',)
+
+    # Display many-to-many in more user-friendly format, in admin list view.
+    filter_horizontal = ('user_permissions', 'groups',)
+
+    # Read only fields for admin detail view.
+    readonly_fields = ('id', 'date_joined', 'last_login')
+
+    # Fieldset organization for admin detail view.
+    fieldsets = (
+        (None, {
+            'fields': ('username', 'first_name', 'last_name', 'email', 'password')
+        }),
+        ('Permissions', {
+            'fields': ('is_active', 'is_staff', 'is_superuser', 'user_permissions', 'groups', 'last_login'),
+        }),
+        ('Advanced', {
+            'classes': ('collapse',),
+            'fields': ('id', 'date_joined')
+        }),
+    )
+
+    class Media:
+        js = ['admin/js/list_filter_collapse.js']
+
+
+class DjangoGroupAdmin(GroupAdmin):
+    """
+    Admin handling for Django's built-in permission "Group" models.
+    """
+    inlines = [GroupUserInline]
+
+    # Fields to display in admin list view.
+    list_display = ('name',)
+    if settings.DEBUG:
+        list_display = ('id',) + list_display
+
+    # Default field ordering in admin list view.
+    ordering = ('name',)
+
+    # Display many-to-many in more user-friendly format, in admin list view.
+    filter_horizontal = ('permissions',)
+
+    # Read only fields for admin detail view.
+    readonly_fields = ('id',)
+
+    # Fieldset organization for admin detail view.
+    fieldsets = (
+        (None, {
+            'fields': ('name', 'permissions')
+        }),
+        ('Advanced', {
+            'classes': ('collapse',),
+            'fields': ('id',)
+        }),
+    )
+
+
+class DjangoPermissionAdmin(admin.ModelAdmin):
+    """
+    Admin handling for Django's built-in "Permission" models.
+    """
+    inlines = [PermissionGroupInline, PermissionUserInline]
+
+    # Fields to display in admin list view.
+    list_display = ('codename', 'name', 'content_type')
+    if settings.DEBUG:
+        list_display = ('id',) + list_display
+
+    # Default field ordering in admin list view.
+    ordering = ('content_type__app_label', 'content_type__model', 'name')
+
+    # Read only fields for admin detail view.
+    readonly_fields = ('id',)
+
+    # Fieldset organization for admin detail view.
+    fieldsets = (
+        (None, {
+            'fields': ('name', 'content_type', 'codename'),
+        }),
+        ('Advanced', {
+            'classes': ('collapse',),
+            'fields': ('id',)
+        }),
+    )
+
+
+class DjangoSessionAdmin(admin.ModelAdmin):
+    """
+    Admin handling for Django's built-in "Session" models.
+    """
+    # Fields to display in admin list view.
+    list_display = ('get_session_user', 'session_key', 'expire_date', 'is_valid')
+
+    # Default ordering in admin views.
+    ordering = ('-expire_date',)
+
+    # Read only fields for admin detail view.
+    readonly_fields = ('get_session_user', 'is_valid')
+
+    # Fieldset organization for admin detail view.
+    fieldsets = (
+        (None, {
+            'fields': ('get_session_user', 'session_key', 'session_data', 'expire_date', 'is_valid'),
+        }),
+    )
+
+    def get_session_data(self, obj):
+        """
+        Alias for getting decoded session data.
+        """
+        # Decode our session data, or attempt to.
+        return_val = obj.get_decoded()
+
+        return return_val
+
+    def is_valid(self, obj):
+        """
+        Returns bool indicating if session is corrupted or not.
+        """
+        if len(self.get_session_data(obj)) > 0:
+            return True
+        else:
+            return False
+    is_valid.boolean = True
+    is_valid.short_description = 'Is Valid (Not Corrupted)'
+
+    def get_session_user(self, obj):
+        """
+        Returns associated user model, or the string 'Anonymous' if session is corrupted or for a non-login.
+        """
+        try:
+            user_id = self.get_session_data(obj)['_auth_user_id']
+            return get_user_model().objects.get(id=user_id)
+        except KeyError:
+            return 'Anonymous'
+    get_session_user.short_description = 'Session User'
+
+# endregion Admin Definitions
+
+
+# Register our updated admin views.
+admin.site.unregister(Group)
+admin.site.register(LogEntry, DjangoLogAdmin)
+admin.site.register(get_user_model(), DjangoUserAdmin)
+admin.site.register(Permission, DjangoPermissionAdmin)
+admin.site.register(Group, DjangoGroupAdmin)
+admin.site.register(Session, DjangoSessionAdmin)
diff --git a/django_v5/test_app/apps.py b/django_v5/test_app/apps.py
new file mode 100644
index 0000000..3f08975
--- /dev/null
+++ b/django_v5/test_app/apps.py
@@ -0,0 +1,11 @@
+"""
+App definition for Django v4.2 test project app.
+"""
+
+# Third-Party Imports.
+from django.apps import AppConfig
+
+
+class TestAppConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'test_app'
diff --git a/django_v5/test_app/forms.py b/django_v5/test_app/forms.py
new file mode 100644
index 0000000..85e2b4e
--- /dev/null
+++ b/django_v5/test_app/forms.py
@@ -0,0 +1,35 @@
+"""
+Forms for Django v4.2 test project app.
+"""
+
+# System Imports.
+
+# Third-Party Imports.
+from django import forms
+
+# Internal Imports.
+
+
+class ApiSendForm(forms.Form):
+    """A single line item for sending in an API call."""
+
+    url = forms.URLField()
+    get_params = forms.CharField(
+        required=False,
+        widget=forms.Textarea(attrs={'rows': '6'}),
+        help_text='Optional URL get param to append to URL.',
+    )
+    header_token = forms.CharField(
+        required=False,
+        widget=forms.Textarea(attrs={'rows': '6'}),
+        help_text='Optional token to put into request header, such as required for API authentication.'
+    )
+    payload = forms.CharField(
+        required=False,
+        widget=forms.Textarea(attrs={'rows': '20'}),
+        help_text=(
+            'Should be in proper JSON dictionary format or else will error. <br><br>'
+            'Ex: Use double quotes instead of single, booleans should be lower case, etc. <br><br>'
+            'If left empty, will send <br>{"success": true}.'
+        )
+    )
diff --git a/django_v5/test_app/management/__init__.py b/django_v5/test_app/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/test_app/management/commands/__init__.py b/django_v5/test_app/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/test_app/management/commands/seed.py b/django_v5/test_app/management/commands/seed.py
new file mode 100644
index 0000000..96c8630
--- /dev/null
+++ b/django_v5/test_app/management/commands/seed.py
@@ -0,0 +1,98 @@
+"""
+Command to generate default project models.
+"""
+
+# Third-Party Imports.
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group, Permission
+from django.contrib.contenttypes.models import ContentType
+from django.core.management.base import BaseCommand
+from django.db.utils import IntegrityError
+
+
+class Command(BaseCommand):
+    help = 'Creates default project models.'
+
+    def handle(self, *args, **kwargs):
+        """
+        The logic of the command.
+        """
+        self.generate_default_users()
+        self.generate_permissions()
+
+    def generate_default_users(self):
+        """Generates default users to login with."""
+        default_password = 'temppass2'
+
+        try:
+            super_user = get_user_model().objects.create(
+                username='test_superuser',
+                first_name='SuperUserFirst',
+                last_name='SuperUserLast',
+                is_superuser=True,
+                is_staff=True,
+                is_active=True,
+            )
+            super_user.set_password(default_password)
+            super_user.save()
+        except IntegrityError:
+            pass
+
+        try:
+            admin_user = get_user_model().objects.create(
+                username='test_admin',
+                first_name='AdminUserFirst',
+                last_name='AdminUserLast',
+                is_superuser=False,
+                is_staff=True,
+                is_active=True,
+            )
+            admin_user.set_password(default_password)
+            admin_user.save()
+        except IntegrityError:
+            pass
+
+        try:
+            inactive_user = get_user_model().objects.create(
+                username='test_inactive',
+                first_name='InactiveUserFirst',
+                last_name='InactiveUserLast',
+                is_superuser=False,
+                is_staff=False,
+                is_active=False,
+            )
+            inactive_user.set_password(default_password)
+            inactive_user.save()
+        except IntegrityError:
+            pass
+
+        try:
+            standard_user = get_user_model().objects.create(
+                username='test_user',
+                first_name='UserFirst',
+                last_name='UserLast',
+                is_superuser=False,
+                is_staff=False,
+                is_active=True,
+            )
+            standard_user.set_password(default_password)
+            standard_user.save()
+        except IntegrityError:
+            pass
+
+    def generate_permissions(self):
+        """Generates default general permission/groups."""
+        try:
+            content_type = ContentType.objects.get_for_model(get_user_model())
+            test_permission = Permission.objects.create(
+                content_type=content_type,
+                codename='test_permission',
+                name='Test Permission',
+            )
+        except IntegrityError:
+            pass
+
+        try:
+            test_group = Group.objects.create(name='test_group')
+        except IntegrityError:
+            pass
diff --git a/django_v5/test_app/migrations/__init__.py b/django_v5/test_app/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/test_app/models.py b/django_v5/test_app/models.py
new file mode 100644
index 0000000..1d2ac81
--- /dev/null
+++ b/django_v5/test_app/models.py
@@ -0,0 +1,77 @@
+"""
+Models for Django v4.2 test project app.
+"""
+
+# Third-Party Imports.
+from django.contrib.auth.models import AbstractUser
+from django.db import models
+from localflavor.us.models import USStateField, USZipCodeField
+
+
+MAX_LENGTH = 255
+
+
+class BaseAbstractModel(models.Model):
+    """Expanded version of the default Django model."""
+
+    # Self-setting/Non-user-editable fields.
+    date_created = models.DateTimeField(auto_now_add=True)
+    date_modified = models.DateTimeField(auto_now=True)
+
+
+class User(AbstractUser):
+    """Custom user model definition.
+    Defined as per the Django docs. Not yet directly used.
+    """
+
+    def clean(self, *args, **kwargs):
+        """
+        Custom cleaning implementation. Includes validation, setting fields, etc.
+        """
+
+    def save(self, *args, **kwargs):
+        """
+        Modify model save behavior.
+        """
+        # Check if new model.
+        creating = False
+        if self._state.adding:
+            creating = True
+
+        # Call parent logic.
+        super().save(*args, **kwargs)
+
+        # If new model, generate corresponding UserProfile object.
+        if creating:
+            UserProfile.objects.create(user=self)
+
+
+class UserProfile(BaseAbstractModel):
+    """Basic model to act as a test fk to user model."""
+
+    # Relationship Keys.
+    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
+
+    # Model Fields.
+    address_1 = models.CharField(max_length=MAX_LENGTH, blank=True)
+    address_2 = models.CharField(max_length=MAX_LENGTH, blank=True)
+    city = models.CharField(max_length=MAX_LENGTH, blank=True)
+    state = USStateField()
+    zipcode = USZipCodeField()
+
+
+class FavoriteFood(BaseAbstractModel):
+    """Basic model to act as a test m2m relation to user model."""
+
+    # Relationship Keys.
+    user = models.ManyToManyField(User)
+
+    # Model fields.
+    name = models.CharField(max_length=MAX_LENGTH)
+
+
+class ApiRequestJson(BaseAbstractModel):
+    """Used to retain data for API testing views."""
+
+    # Model fields.
+    json_value = models.JSONField(default=dict)
diff --git a/django_v5/test_app/templates/registration/login.html b/django_v5/test_app/templates/registration/login.html
new file mode 100644
index 0000000..88b9d72
--- /dev/null
+++ b/django_v5/test_app/templates/registration/login.html
@@ -0,0 +1,48 @@
+{% extends 'test_app/base.html' %}
+
+
+{% block page_header %}
+  Django LTS v4.2 - Login Page
+{% endblock page_header %}
+
+
+{% block page_subheader %}
+  You need to login to continue.
+{% endblock page_subheader %}
+
+
+{% block content %}
+
+  {% if form.errors %}
+    <p>Your username and password didn't match. Please try again.</p>
+  {% endif %}
+
+  {% if next %}
+    {% if user.is_authenticated %}
+      <p>
+        Your account doesn't have access to this page. To proceed,
+        please login with an account that has access.
+      </p>
+    {% else %}
+      <p>Please login to see this page.</p>
+    {% endif %}
+  {% endif %}
+
+  <form method="post" action="{% url 'login' %}">
+    {% csrf_token %}
+    <table>
+      <tr>
+        <td>{{ form.username.label_tag }}</td>
+        <td>{{ form.username }}</td>
+      </tr>
+      <tr>
+        <td>{{ form.password.label_tag }}</td>
+        <td>{{ form.password }}</td>
+      </tr>
+    </table>
+
+    <input type="submit" value="login">
+    <input type="hidden" name="next" value="{{ next }}">
+  </form>
+
+{% endblock %}
diff --git a/django_v5/test_app/templates/test_app/api_send.html b/django_v5/test_app/templates/test_app/api_send.html
new file mode 100644
index 0000000..48d1b13
--- /dev/null
+++ b/django_v5/test_app/templates/test_app/api_send.html
@@ -0,0 +1,346 @@
+{% extends "test_app/base.html" %}
+
+
+{% block page_header %}
+  Django LTS v4.2 - API Send
+{% endblock page_header %}
+
+
+{% block page_subheader %}
+  API Send Page
+{% endblock page_subheader %}
+
+
+{% block stylesheets %}
+<style>
+  form h2, form h3, form h4, form h5, form h6,
+  .result-box h2, .result-box h3, .result-box h4, .result-box h5, .result-box h6,
+  .example h2, .example h3, .example h4, .example h5, .example h6 {
+    margin-top: 6px;
+    margin-bottom: 6px;
+  }
+
+  form {
+    margin: 10px;
+    padding: 15px;
+
+    background-color: #e1e0eb;
+    border: 1px solid grey;
+  }
+
+  form div {
+    padding-top: 5px;
+    padding-bottom: 5px;
+  }
+
+  input {
+    width: 100%;
+  }
+
+  textarea {
+    width: 100%;
+  }
+
+  input {
+    margin-top: 5px;
+  }
+
+  pre {
+    width: 100%;
+    margin: 5px;
+    padding: 5px;
+    background-color: LightSteelBlue;
+    border: 1px solid grey;
+  }
+
+  pre.allow-break {
+    white-space: pre-wrap;
+  }
+
+  .error {
+    color: red;
+  }
+
+  .field-group {
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+  }
+
+  .label p {
+    margin-top: 8px;
+    margin-bottom: 8px;
+  }
+
+  .label {
+    width: 10%;
+    padding: 0 10px 0 10px;
+    text-align: right;
+  }
+
+  .help-text {
+    font-size: 70%;
+    font-style: italic;
+    text-align: center;
+  }
+
+  .field {
+    width: 80%;
+    padding: 0 10px 0 10px;
+  }
+
+  .submit-buttons {
+    display: flex;
+    flex-direction: row;
+  }
+
+  .submit-buttons input {
+    margin: 5px;
+    padding: 5px;
+  }
+
+  .example {
+    margin-top: 25px;
+    margin-right: 10px;
+    margin-bottom: 25px;
+    margin-left: 10px;
+    padding: 15px;
+
+    background-color: #e1e0eb;
+    border: 1px solid grey;
+  }
+
+  .result-box {
+    margin-top: 25px;
+    margin-right: 10px;
+    margin-bottom: 25px;
+    margin-left: 10px;
+    padding: 15px;
+
+    background-color: #e1e0eb;
+    border: 1px solid grey;
+  }
+
+  .result-box.sent-data pre {
+    background-color: LightBlue;
+  }
+
+  .italics {
+    margin-top: 5px;
+    margin-bottom: 8px;
+
+    font-size: 90%;
+    font-style: italic;
+    color: #575757;
+  }
+
+  h3.success-return {
+    color: DarkGreen;
+  }
+  div.success-return pre {
+    background-color: #cde4e4;
+  }
+
+  h3.error-return {
+    color: DarkRed;
+  }
+  div.error-return pre {
+    background-color: #d9cde4;
+  }
+</style>
+{% endblock stylesheets %}
+
+
+{% block content %}
+  <p>Use this to generate and send test API requests to other projects.</p>
+
+  <form method="POST">
+    <h2>API Send Form</h2>
+    <p class="italics">Use the below form to send a JSON API ping to the desired url.</p>
+
+    {% csrf_token %}
+
+    {% if form.non_field_errors %}
+      <div class="error">
+        <p>Non Field Errors:</p>
+        {{ form.non_field_errors }}
+      </div>
+      <hr>
+    {% endif %}
+
+    {% for field in form %}
+
+      <div>
+        {% if field.errors %}
+          <p class="error"> Field Error:
+          {% for error in field.errors %}
+            {{ error }}
+          {% endfor %}
+          </p>
+        {% endif %}
+
+        <div class="field-group">
+
+          <div class="label">
+            <p>{{ field.label }}:</p>
+            {% if field.help_text %}
+              <p class="help-text">{{ field.help_text|safe }}</p>
+            {% endif %}
+          </div>
+          <div class="field">
+            {{ field }}
+          </div>
+        </div>
+
+      </div>
+
+    {% endfor %}
+
+    <div class="submit-buttons">
+      <input
+        type="submit"
+        name="submit_get"
+        value="Submit as GET"
+        title="Generally used to retrieve data from the server."
+      >
+      <input
+        type="submit"
+        name="submit_post"
+        value="Submit as POST"
+        title="Generally used to send data to the server, and create a new resource."
+      >
+      <input
+        type="submit"
+        name="submit_put"
+        value="Submit as PUT"
+        title="Generally used to send data to the server, and update an existing resource by full replacement."
+      >
+      <input
+        type="submit"
+        name="submit_patch"
+        value="Submit as PATCH"
+        title="Generally used to send data to the server, and update an existing resource by partial replacement."
+      >
+      <input
+        type="submit"
+        name="submit_delete"
+        value="Submit as DELETE"
+        title="Generally used to send data to the server, and delete an existing resource."
+      >
+    </div>
+  </form>
+
+  <div class="result-box">
+    <h2>Parsed Return-Response</h2>
+
+    {% if response_error or response_success %}
+      <p class="italics">This is the data that was returned after the previous API send.</p>
+    {% endif %}
+
+    {% if response_success %}
+      <h3 class="success-return">Success Sending API Ping</h3>
+      {% for key, value in response_success.items %}
+        <div class="field-group success-return">
+          <div class="label">
+            <p>{{ key }}</p>
+          </div>
+          <pre class="allow-break">{{ value }}</pre>
+        </div>
+      {% endfor %}
+    {% endif %}
+
+    {% if response_error %}
+      <h3 class="error-return">Error Sending API Ping</h3>
+      {% for key, value in response_error.items %}
+        <div class="field-group error-return">
+          <div class="label">
+            <p>{{ key }}</p>
+          </div>
+          <pre class="allow-break">{{ value }}</pre>
+        </div>
+      {% endfor %}
+    {% endif %}
+
+    {% if not response_error and not response_success %}
+      <p class="italics">No return value yet. Submit the API form and the resulting return response will display here.</p>
+    {% endif %}
+  </div>
+
+  {% if sent_data %}
+    <div class="result-box sent-data">
+      <h2>Sent Data</h2>
+      <p class="italics">This is what was sent out from this form, on the previous API call.</p>
+      {% for key, value in sent_data.items %}
+        <div class="field-group">
+          <div class="label">
+            <p>{{ key }}</p>
+          </div>
+          <pre class="allow-break">{{ value }}</pre>
+        </div>
+      {% endfor %}
+    </div>
+  {% endif %}
+
+  <div class="example">
+    <h2>Example Send Values:</h2>
+
+    <p>Below are some example form values to get started.</p>
+
+    <div class="field-group">
+      <div class="label">
+        <p>
+          Url:
+        </p>
+      </div>
+      <pre>http://127.0.0.1:8000/test_app/api/parse/</pre>
+    </div>
+    <div class="field-group">
+      <div class="label">
+        <p>
+          Get Params:
+        </p>
+      </div>
+      <pre>test-param-1=Abc&test-param-2=123</pre>
+    </div>
+    <div class="field-group">
+      <div class="label">
+        <p>
+          Header Token:
+        </p>
+      </div>
+      <pre>MyExampleHeaderAuthToken-112233445566778899ABABCDCD</pre>
+    </div>
+    <div class="field-group">
+      <div class="label">
+        <p>
+          Payload:
+        </p>
+      </div>
+      <pre>{
+  "test": true,
+  "Aaa": "Bbb",
+  "MyNumber": 5
+}</pre>
+    </div>
+
+  <hr>
+
+  <p>Above values will send:</p>
+  <div class="field-group">
+    <pre>url: http://127.0.0.1:8000/test_app/api/parse/?test-param-1=Abc&test-param-2=123
+
+header: {
+  "Accept": "application/json",
+  "token": "MyExampleHeaderAuthToken-112233445566778899ABABCDCD"
+}
+
+data: {
+  "test": true,
+  "Aaa": "Bbb",
+  "MyNumber": 5
+}</pre>
+  </div>
+
+  </div>
+
+{% endblock content %}
diff --git a/django_v5/test_app/templates/test_app/base.html b/django_v5/test_app/templates/test_app/base.html
new file mode 100644
index 0000000..e339200
--- /dev/null
+++ b/django_v5/test_app/templates/test_app/base.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="utf-8"/>
+  <title>
+    Django LTS v4.2 -
+    {% block title %}
+      Test App Title
+    {% endblock title %}
+  </title>
+
+  {% block stylesheets %}{% endblock stylesheets %}
+  {% block scripts %}{% endblock scripts %}
+</head>
+
+<body>
+  {% if messages %}
+    <ul class="messages">
+      {% for message in messages %}
+        <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
+          {{ message }}
+        </li>
+      {% endfor %}
+    </ul>
+  {% endif %}
+
+  {% block body %}
+    <h1>
+      {% block page_header %}
+        Django LTS v4.2 - Test Page Header
+      {% endblock page_header %}
+    </h1>
+
+    <h2>
+      {% block page_subheader %}
+        Test Page Subheader
+      {% endblock page_subheader %}
+    </h2>
+
+    <div>
+      {% block content %}
+        <p>Test Page Content</p>
+      {% endblock content %}
+    </div>
+  {% endblock body%}
+</body>
+
+</html>
diff --git a/django_v5/test_app/templates/test_app/group_check.html b/django_v5/test_app/templates/test_app/group_check.html
new file mode 100644
index 0000000..4a41ace
--- /dev/null
+++ b/django_v5/test_app/templates/test_app/group_check.html
@@ -0,0 +1,16 @@
+{% extends "test_app/base.html" %}
+
+
+{% block page_header %}
+  Django LTS v4.2 - Test Group Check
+{% endblock page_header %}
+
+
+{% block page_subheader %}
+  This view should require group of "test_group" to see.
+{% endblock page_subheader %}
+
+
+{% block content %}
+  <p><a href="{% url 'test_app:index' %}">Back to Test App Views</a></p>
+{% endblock content %}
diff --git a/django_v5/test_app/templates/test_app/index.html b/django_v5/test_app/templates/test_app/index.html
new file mode 100644
index 0000000..ea356cd
--- /dev/null
+++ b/django_v5/test_app/templates/test_app/index.html
@@ -0,0 +1,55 @@
+{% extends "test_app/base.html" %}
+
+{% block page_header %}
+  Django LTS v4.2 - Test App Index
+{% endblock page_header %}
+
+
+{% block content %}
+  <p>Test Page Content</p>
+
+  {% if is_class_view %}
+    <p><a href="{% url 'test_app:index' %}">Render page from Function view</a></p>
+  {% else %}
+    <p><a href="{% url 'test_app:index_as_class' %}">Render page from Class view</a></p>
+  {% endif %}
+
+  <ul>
+    <li>
+      <p>Standard Views:</p>
+      <ul>
+        <li>
+          <p><a href="{% url 'root_project_home_page:index' %}">Back to Django v4.2 Home</a></p>
+        </li>
+        <li>
+          <p><a href="{% url 'test_app:view_with_login_check' %}">Test App - Login Check Page</a></p>
+        </li>
+        <li>
+          <p><a href="{% url 'test_app:view_with_permission_check' %}">Test App - Permission Check Page</a></p>
+        </li>
+        <li>
+          <p><a href="{% url 'test_app:view_with_group_check' %}">Test App - Group Check Page</a></p>
+        </li>
+      </ul>
+    </li>
+    <li>
+      <p>API Views:</p>
+      <ul>
+        <li>
+          <p><a href="{% url 'test_app:api_parse' %}">API Parse - Receive API requests here to parse them.</a></p>
+        </li>
+        <li>
+          <p><a href="{% url 'test_app:api_display' %}">API Display - View parsed API requests here.</a></p>
+          <p>
+            Note: Only displays the first received API request since last access of api_display view.
+            <br>
+            All parsed API data is purged after page access.
+          </p>
+        </li>
+        <li>
+          <p><a href="{% url 'test_app:api_send' %}">API Send - Generate and send API requests here.</a></p>
+        </li>
+      </ul>
+    </li>
+  </ul>
+{% endblock content %}
diff --git a/django_v5/test_app/templates/test_app/login_check.html b/django_v5/test_app/templates/test_app/login_check.html
new file mode 100644
index 0000000..35f9971
--- /dev/null
+++ b/django_v5/test_app/templates/test_app/login_check.html
@@ -0,0 +1,16 @@
+{% extends "test_app/base.html" %}
+
+
+{% block page_header %}
+  Django LTS v4.2 - Test Login Check
+{% endblock page_header %}
+
+
+{% block page_subheader %}
+  This view should require user login to see.
+{% endblock page_subheader %}
+
+
+{% block content %}
+  <p><a href="{% url 'test_app:index' %}">Back to Test App Views</a></p>
+{% endblock content %}
diff --git a/django_v5/test_app/templates/test_app/permission_check.html b/django_v5/test_app/templates/test_app/permission_check.html
new file mode 100644
index 0000000..336a97e
--- /dev/null
+++ b/django_v5/test_app/templates/test_app/permission_check.html
@@ -0,0 +1,16 @@
+{% extends "test_app/base.html" %}
+
+
+{% block page_header %}
+  Django LTS v4.2 - Test Permission Check
+{% endblock page_header %}
+
+
+{% block page_subheader %}
+  This view should require permission of "test_permission" to see.
+{% endblock page_subheader %}
+
+
+{% block content %}
+  <p><a href="{% url 'test_app:index' %}">Back to Test App Views</a></p>
+{% endblock content %}
diff --git a/django_v5/test_app/templates/test_app/root_project_home_page.html b/django_v5/test_app/templates/test_app/root_project_home_page.html
new file mode 100644
index 0000000..391ab4f
--- /dev/null
+++ b/django_v5/test_app/templates/test_app/root_project_home_page.html
@@ -0,0 +1,50 @@
+{% extends "test_app/base.html" %}
+
+
+{% block page_header %}
+  Django Testing Sandbox - LTS v4.2
+{% endblock page_header %}
+
+
+{% block content %}
+  <hr>
+
+  <h3>In-Project Views</h3>
+  <ul>
+    <li>
+      <p><a href="{% url 'admin:index' %}">Admin Views</a></p>
+    </li>
+    <li>
+      <p><a href="{% url 'test_app:index' %}">Test App Views</a></p>
+    </li>
+  </ul>
+
+  <hr>
+
+  <h3>Package Debug Views</h3>
+
+  <h4>Django AdminLTE2 PDQ (PrettyDarnQuick)</h4>
+  <ul>
+    <li>
+      <p><a href="{% url 'adminlte2_pdq:home' %}">AdminLTE2 PDQ Debug Views</a></p>
+    </li>
+  </ul>
+
+  <h4>Django DD (DumpDie)</h4>
+  <ul>
+    <li>
+      <p><a href="{% url 'django_dump_die:index' %}">Django DD Debug Views</a></p>
+    </li>
+    <li>
+      <p><a href="{% url 'django_dump_die_tests:index' %}">Django DD Test Views</a></p>
+    </li>
+  </ul>
+
+  <h4>Django ETC (ExpandedTestCases)</h4>
+  <ul>
+    <li>
+      <p><a href="{% url 'django_expanded_test_cases:index' %}">Django ETC Test Views</a></p>
+    </li>
+  </ul>
+
+{% endblock content %}
diff --git a/django_v5/test_app/tests/__init__.py b/django_v5/test_app/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/test_app/tests/base_tests/__init__.py b/django_v5/test_app/tests/base_tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/test_app/tests/base_tests/test_models.py b/django_v5/test_app/tests/base_tests/test_models.py
new file mode 100644
index 0000000..8758f1c
--- /dev/null
+++ b/django_v5/test_app/tests/base_tests/test_models.py
@@ -0,0 +1,253 @@
+"""
+Model tests for Django v4.2 test project app.
+
+Uses base/built-in Django logic to execute.
+"""
+
+# Third-Party Imports.
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import AnonymousUser
+from django.shortcuts import reverse
+from django.test import TestCase
+
+# Internal Imports.
+from test_app.models import UserProfile
+
+
+class ModelTestCase(TestCase):
+    """Tests for app models."""
+
+    @classmethod
+    def setUpTestData(cls):
+        """Set up testing data."""
+        # Call parent logic.
+        super().setUpTestData()
+
+        # Generate user models.
+        cls.test_super_user = get_user_model().objects.create(
+            username='test_superuser',
+            first_name='SuperUserFirst',
+            last_name='SuperUserLast',
+            is_superuser=True,
+            is_staff=False,
+            is_active=True,
+        )
+        cls.test_admin_user = get_user_model().objects.create(
+            username='test_admin',
+            first_name='AdminUserFirst',
+            last_name='AdminUserLast',
+            is_superuser=False,
+            is_staff=True,
+            is_active=True,
+        )
+        cls.test_inactive_user = get_user_model().objects.create(
+            username='test_inactive',
+            first_name='InactiveUserFirst',
+            last_name='InactiveUserLast',
+            is_superuser=False,
+            is_staff=False,
+            is_active=False,
+        )
+        cls.test_standard_user = get_user_model().objects.create(
+            username='test_user',
+            first_name='UserFirst',
+            last_name='UserLast',
+            is_superuser=False,
+            is_staff=False,
+            is_active=True,
+        )
+
+    def debug_data(self, response):
+        print('\n\n\n\n')
+        self.display_content(response)
+        print('\n\n')
+        self.display_context(response)
+        print('\n\n')
+        self.display_session()
+        print('\n\n\n\n')
+
+    def display_content(self, response):
+        """Prints out all page content to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.content'))
+
+        print(response.content.decode('utf-8'))
+
+    def display_context(self, response):
+        """Prints out all context values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.context'))
+
+        try:
+            if response.context is not None:
+                for key in response.context.keys():
+                    context_value = str(response.context.get(key))
+                    # Truncate display if very long.
+                    if len(context_value) > 80:
+                        context_value = '"{0}"..."{1}"'.format(context_value[:40], context_value[-40:])
+                    print('    * {0}: {1}'.format(key, context_value))
+        except AttributeError:
+            pass
+
+    def display_session(self):
+        """Prints out all session values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'client.session'))
+
+        for key, value in self.client.session.items():
+            print('    * {0}: {1}'.format(key, value))
+
+    def test__user_model_creation(self):
+        """Verifies that expected user model properly generates."""
+        with self.subTest('Check user creation using super user'):
+            self.assertIsNotNone(self.test_super_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_super_user)
+            self.assertEqual(user_profile, self.test_super_user.profile)
+
+        with self.subTest('Check user creation using admin user'):
+            self.assertIsNotNone(self.test_admin_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_admin_user)
+            self.assertEqual(user_profile, self.test_admin_user.profile)
+
+        with self.subTest('Check user creation using inactive user'):
+            self.assertIsNotNone(self.test_inactive_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_inactive_user)
+            self.assertEqual(user_profile, self.test_inactive_user.profile)
+
+        with self.subTest('Check user creation using standard user'):
+            self.assertIsNotNone(self.test_standard_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_standard_user)
+            self.assertEqual(user_profile, self.test_standard_user.profile)
+
+        with self.subTest('Check user creation using new user'):
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+            self.assertIsNotNone(new_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=new_user)
+            self.assertEqual(user_profile, new_user.profile)
+
+    def test__assert_login(self):
+        """Verifies that expected user model properly logs in."""
+        with self.subTest('Check login using super user'):
+            # Get response object.
+            self.client.force_login(self.test_super_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(self.test_super_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_super_user, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(self.test_super_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_super_user, response.wsgi_request.user)
+
+        with self.subTest('Check login using admin user'):
+            # Get response object.
+            self.client.force_login(self.test_admin_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(self.test_admin_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_admin_user, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(self.test_admin_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_admin_user, response.wsgi_request.user)
+
+        with self.subTest('Check login using inactive user'):
+            # Get response object.
+            self.client.force_login(self.test_inactive_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Get response's lazy user object.
+            uwsgi_user = response.wsgi_request.user
+            if hasattr(uwsgi_user, '_wrapped') and hasattr(uwsgi_user, '_setup'):
+                if uwsgi_user._wrapped.__class__ == object:
+                    uwsgi_user._setup()
+                uwsgi_user = uwsgi_user._wrapped
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(self.test_inactive_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertTrue(isinstance(uwsgi_user, AnonymousUser))
+            self.assertFalse(isinstance(uwsgi_user, get_user_model()))
+            self.assertNotEqual(self.test_inactive_user, uwsgi_user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(self.test_inactive_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertTrue(isinstance(uwsgi_user, AnonymousUser))
+            self.assertFalse(isinstance(uwsgi_user, get_user_model()))
+            self.assertNotEqual(self.test_inactive_user, uwsgi_user)
+
+        with self.subTest('Check login using standard user'):
+            # Get response object.
+            self.client.force_login(self.test_standard_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(self.test_standard_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_standard_user, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(self.test_standard_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_standard_user, response.wsgi_request.user)
+
+        with self.subTest('Check login using new user'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            self.client.force_login(new_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(new_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(new_user, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(new_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(new_user, response.wsgi_request.user)
diff --git a/django_v5/test_app/tests/base_tests/test_views.py b/django_v5/test_app/tests/base_tests/test_views.py
new file mode 100644
index 0000000..06c000e
--- /dev/null
+++ b/django_v5/test_app/tests/base_tests/test_views.py
@@ -0,0 +1,675 @@
+"""
+View tests for Django v4.2 test project app.
+
+Uses base/built-in Django logic to execute.
+"""
+
+# Third-Party Imports.
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group, Permission
+from django.contrib.contenttypes.models import ContentType
+from django.shortcuts import reverse
+from django.test import TestCase
+
+
+class ViewTestCase(TestCase):
+    """Tests for app views."""
+
+    @classmethod
+    def setUpTestData(cls):
+        """Set up testing data."""
+        # Call parent logic.
+        super().setUpTestData()
+
+        # Generate user models.
+        cls.test_super_user = get_user_model().objects.create(
+            username='test_superuser',
+            first_name='SuperUserFirst',
+            last_name='SuperUserLast',
+            is_superuser=True,
+            is_staff=False,
+            is_active=True,
+        )
+        cls.test_admin_user = get_user_model().objects.create(
+            username='test_admin',
+            first_name='AdminUserFirst',
+            last_name='AdminUserLast',
+            is_superuser=False,
+            is_staff=True,
+            is_active=True,
+        )
+        cls.test_inactive_user = get_user_model().objects.create(
+            username='test_inactive',
+            first_name='InactiveUserFirst',
+            last_name='InactiveUserLast',
+            is_superuser=False,
+            is_staff=False,
+            is_active=False,
+        )
+        cls.test_standard_user = get_user_model().objects.create(
+            username='test_user',
+            first_name='UserFirst',
+            last_name='UserLast',
+            is_superuser=False,
+            is_staff=False,
+            is_active=True,
+        )
+
+    def debug_data(self, response):
+        print('\n\n\n\n')
+        self.display_content(response)
+        print('\n\n')
+        self.display_context(response)
+        print('\n\n')
+        self.display_session()
+        print('\n\n\n\n')
+
+    def display_content(self, response):
+        """Prints out all page content to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.content'))
+
+        print(response.content.decode('utf-8'))
+
+    def display_context(self, response):
+        """Prints out all context values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.context'))
+
+        try:
+            if response.context is not None:
+                for key in response.context.keys():
+                    context_value = str(response.context.get(key))
+                    # Truncate display if very long.
+                    if len(context_value) > 80:
+                        context_value = '"{0}"..."{1}"'.format(context_value[:40], context_value[-40:])
+                    print('    * {0}: {1}'.format(key, context_value))
+        except AttributeError:
+            pass
+
+    def display_session(self):
+        """Prints out all session values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'client.session'))
+
+        for key, value in self.client.session.items():
+            print('    * {0}: {1}'.format(key, value))
+
+    def test__assert_index_view(self):
+        """Verifies that index view can be accessed as expected."""
+        with self.subTest('Check views using super user'):
+            # Get response object.
+            self.client.force_login(self.test_super_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using admin user'):
+            # Get response object.
+            self.client.force_login(self.test_admin_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using inactive user'):
+            # Get response object.
+            self.client.force_login(self.test_inactive_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using standard user'):
+            # Get response object.
+            self.client.force_login(self.test_standard_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using new user'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            self.client.force_login(new_user)
+            response = self.client.get(reverse('test_app:index'))
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+    def test__assert_login_view(self):
+        """Verifies that login view can be accessed as expected."""
+        with self.subTest('Check views without login'):
+            # Get response object.
+            response = self.client.get(reverse('test_app:view_with_login_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using super user'):
+            # Get response object.
+            self.client.force_login(self.test_super_user)
+            response = self.client.get(reverse('test_app:view_with_login_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using admin user'):
+            # Get response object.
+            self.client.force_login(self.test_admin_user)
+            response = self.client.get(reverse('test_app:view_with_login_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using inactive user'):
+            # Get response object.
+            self.client.force_login(self.test_inactive_user)
+            response = self.client.get(reverse('test_app:view_with_login_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user'):
+            # Get response object.
+            self.client.force_login(self.test_standard_user)
+            response = self.client.get(reverse('test_app:view_with_login_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using new user'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            self.client.force_login(new_user)
+            response = self.client.get(reverse('test_app:view_with_login_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+    def test__assert_permission_view(self):
+        """Verifies that permission view can be accessed as expected."""
+
+        # Create required permission.
+        content_type = ContentType.objects.get_for_model(get_user_model())
+        test_permission = Permission.objects.create(
+            content_type=content_type,
+            codename='test_permission',
+            name='Test Permission',
+        )
+
+        with self.subTest('Check views without login'):
+            # Get response object.
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using super user - Without correct permission'):
+            # Get response object.
+            self.client.force_login(self.test_super_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using super user - With correct permission'):
+            # Add permission to user.
+            self.test_super_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            self.client.force_login(self.test_super_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using admin user - Without correct permission'):
+            # Get response object.
+            self.client.force_login(self.test_admin_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using admin user - With correct permission'):
+            # Add permission to user.
+            self.test_admin_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            self.client.force_login(self.test_admin_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using inactive user - Without correct permission'):
+            # Get response object.
+            self.client.force_login(self.test_inactive_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using inactive user - With correct permission'):
+            # Add permission to user.
+            self.test_inactive_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            self.client.force_login(self.test_inactive_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - Without correct permission'):
+            # Get response object.
+            self.client.force_login(self.test_standard_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - With correct permission'):
+            # Add permission to user.
+            self.test_standard_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            self.client.force_login(self.test_standard_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using new user - Without correct permission'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            self.client.force_login(new_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using new user - With correct permission'):
+            # Add permission to user.
+            new_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            self.client.force_login(new_user)
+            response = self.client.get(reverse('test_app:view_with_permission_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+    def test__assert_group_view(self):
+        """Verifies that group view can be accessed as expected."""
+
+        # Create required group.
+        test_group = Group.objects.create(name='test_group')
+
+        with self.subTest('Check views without login'):
+            # Get response object.
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using super user - Without correct group'):
+            # Get response object.
+            self.client.force_login(self.test_super_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using super user - With correct group'):
+            # Add group to user.
+            self.test_super_user.groups.add(test_group)
+
+            # Get response object.
+            self.client.force_login(self.test_super_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using admin user - Without correct group'):
+            # Get response object.
+            self.client.force_login(self.test_admin_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertNotIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertNotIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using admin user - With correct group'):
+            # Add group to user.
+            self.test_admin_user.groups.add(test_group)
+
+            # Get response object.
+            self.client.force_login(self.test_admin_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using inactive user - Without correct group'):
+            # Get response object.
+            self.client.force_login(self.test_inactive_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using inactive user - With correct group'):
+            # Add group to user.
+            self.test_inactive_user.groups.add(test_group)
+
+            # Get response object.
+            self.client.force_login(self.test_inactive_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - Without correct group'):
+            # Get response object.
+            self.client.force_login(self.test_standard_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertNotIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertNotIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - With correct group'):
+            # Add group to user.
+            self.test_standard_user.groups.add(test_group)
+
+            # Get response object.
+            self.client.force_login(self.test_standard_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using new user - Without correct group'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            self.client.force_login(new_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertNotIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertNotIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using new user - With correct group'):
+            # Add group to user.
+            new_user.groups.add(test_group)
+
+            # Get response object.
+            self.client.force_login(new_user)
+            response = self.client.get(reverse('test_app:view_with_group_check'), follow=True)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
diff --git a/django_v5/test_app/tests/etc_tests/__init__.py b/django_v5/test_app/tests/etc_tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/django_v5/test_app/tests/etc_tests/test_models.py b/django_v5/test_app/tests/etc_tests/test_models.py
new file mode 100644
index 0000000..5358c9c
--- /dev/null
+++ b/django_v5/test_app/tests/etc_tests/test_models.py
@@ -0,0 +1,219 @@
+"""
+Model tests for Django v4.2 test project app.
+
+Uses ETC package logic to execute.
+Should otherwise be fairly similar to the "base_tests", as a way to
+double-check that the ETC package functions as expected.
+"""
+
+# Third-Party Imports.
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import AnonymousUser
+from django_expanded_test_cases import IntegrationTestCase
+
+# Internal Imports.
+from test_app.models import UserProfile
+
+
+class ModelTestCase(IntegrationTestCase):
+    """Tests for app models."""
+
+    @classmethod
+    def setUpTestData(cls):
+        """Set up testing data."""
+        # Call parent logic.
+        super().setUpTestData()
+
+    def debug_data(self, response):
+        print('\n\n\n\n')
+        self.display_content(response)
+        print('\n\n')
+        self.display_context(response)
+        print('\n\n')
+        self.display_session()
+        print('\n\n\n\n')
+
+    def display_content(self, response):
+        """Prints out all page content to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.content'))
+
+        print(response.content.decode('utf-8'))
+
+    def display_context(self, response):
+        """Prints out all context values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.context'))
+
+        try:
+            if response.context is not None:
+                for key in response.context.keys():
+                    context_value = str(response.context.get(key))
+                    # Truncate display if very long.
+                    if len(context_value) > 80:
+                        context_value = '"{0}"..."{1}"'.format(context_value[:40], context_value[-40:])
+                    print('    * {0}: {1}'.format(key, context_value))
+        except AttributeError:
+            pass
+
+    def display_session(self):
+        """Prints out all session values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'client.session'))
+
+        for key, value in self.client.session.items():
+            print('    * {0}: {1}'.format(key, value))
+
+    def test__user_model_creation(self):
+        """Verifies that expected user model properly generates."""
+        with self.subTest('Check user creation using super user'):
+            self.assertIsNotNone(self.test_superuser.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_superuser)
+            self.assertEqual(user_profile, self.test_superuser.profile)
+
+        with self.subTest('Check user creation using admin user'):
+            self.assertIsNotNone(self.test_admin.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_admin)
+            self.assertEqual(user_profile, self.test_admin.profile)
+
+        with self.subTest('Check user creation using inactive user'):
+            self.assertIsNotNone(self.test_inactive_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_inactive_user)
+            self.assertEqual(user_profile, self.test_inactive_user.profile)
+
+        with self.subTest('Check user creation using standard user'):
+            self.assertIsNotNone(self.test_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=self.test_user)
+            self.assertEqual(user_profile, self.test_user.profile)
+
+        with self.subTest('Check user creation using new user'):
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+            self.assertIsNotNone(new_user.profile)
+
+            # Get corresponding auto-created profile model.
+            user_profile = UserProfile.objects.get(user=new_user)
+            self.assertEqual(user_profile, new_user.profile)
+
+    def test__assert_login(self):
+        """Verifies that expected user model properly logs in."""
+        with self.subTest('Check login using super user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_superuser)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(self.test_superuser.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_superuser, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(self.test_superuser.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_superuser, response.wsgi_request.user)
+
+        with self.subTest('Check login using admin user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_admin)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(self.test_admin.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_admin, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(self.test_admin.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_admin, response.wsgi_request.user)
+
+        with self.subTest('Check login using inactive user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_inactive_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Get response's lazy user object.
+            uwsgi_user = response.wsgi_request.user
+            if hasattr(uwsgi_user, '_wrapped') and hasattr(uwsgi_user, '_setup'):
+                if uwsgi_user._wrapped.__class__ == object:
+                    uwsgi_user._setup()
+                uwsgi_user = uwsgi_user._wrapped
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            # self.assertEqual(self.test_inactive_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertTrue(isinstance(uwsgi_user, AnonymousUser))
+            self.assertFalse(isinstance(uwsgi_user, get_user_model()))
+            self.assertNotEqual(self.test_inactive_user, uwsgi_user)
+            self.assertTrue(isinstance(response.user, AnonymousUser))
+            self.assertFalse(isinstance(response.user, get_user_model()))
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            # self.assertEqual(self.test_inactive_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertTrue(isinstance(uwsgi_user, AnonymousUser))
+            self.assertFalse(isinstance(uwsgi_user, get_user_model()))
+            self.assertNotEqual(self.test_inactive_user, uwsgi_user)
+            self.assertTrue(isinstance(response.user, AnonymousUser))
+            self.assertFalse(isinstance(response.user, get_user_model()))
+
+        with self.subTest('Check login using standard user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(self.test_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_user, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(self.test_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(self.test_user, response.wsgi_request.user)
+
+        with self.subTest('Check login using new user'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=new_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks, of different ways to ensure expected user is logged in.
+            self.assertEqual(new_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(new_user, response.wsgi_request.user)
+
+            # Try again, to make sure that accessing any of the above values didn't somehow clear the client.
+            self.assertEqual(new_user.pk, int(self.client.session.get('_auth_user_id', None)))
+            self.assertFalse(isinstance(response.wsgi_request.user, AnonymousUser))
+            self.assertTrue(isinstance(response.wsgi_request.user, get_user_model()))
+            self.assertEqual(new_user, response.wsgi_request.user)
diff --git a/django_v5/test_app/tests/etc_tests/test_views.py b/django_v5/test_app/tests/etc_tests/test_views.py
new file mode 100644
index 0000000..5458620
--- /dev/null
+++ b/django_v5/test_app/tests/etc_tests/test_views.py
@@ -0,0 +1,613 @@
+"""
+View tests for Django v4.2 test project app.
+
+Uses ETC package logic to execute.
+Should otherwise be fairly similar to the "base_tests", as a way to
+double-check that the ETC package functions as expected.
+"""
+
+# Third-Party Imports.
+from django.contrib.auth import get_user_model
+from django.contrib.auth.models import Group, Permission
+from django.contrib.contenttypes.models import ContentType
+from django.shortcuts import reverse
+from django_expanded_test_cases import IntegrationTestCase
+
+
+class ViewTestCase(IntegrationTestCase):
+    """Tests for app views."""
+
+    @classmethod
+    def setUpTestData(cls):
+        """Set up testing data."""
+        # Call parent logic.
+        super().setUpTestData()
+
+    def debug_data(self, response):
+        print('\n\n\n\n')
+        self.display_content(response)
+        print('\n\n')
+        self.display_context(response)
+        print('\n\n')
+        self.display_session()
+        print('\n\n\n\n')
+
+    def display_content(self, response):
+        """Prints out all page content to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.content'))
+
+        print(response.content.decode('utf-8'))
+
+    def display_context(self, response):
+        """Prints out all context values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'response.context'))
+
+        try:
+            if response.context is not None:
+                for key in response.context.keys():
+                    context_value = str(response.context.get(key))
+                    # Truncate display if very long.
+                    if len(context_value) > 80:
+                        context_value = '"{0}"..."{1}"'.format(context_value[:40], context_value[-40:])
+                    print('    * {0}: {1}'.format(key, context_value))
+        except AttributeError:
+            pass
+
+    def display_session(self):
+        """Prints out all session values to terminal."""
+        print('{0} {1} {0}'.format('=' * 10, 'client.session'))
+
+        for key, value in self.client.session.items():
+            print('    * {0}: {1}'.format(key, value))
+
+    def test__assert_index_view(self):
+        """Verifies that index view can be accessed as expected."""
+        with self.subTest('Check views using super user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_superuser)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using admin user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_admin)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using inactive user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_inactive_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using standard user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=self.test_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+        with self.subTest('Check views using new user'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:index', user=new_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test App Index', page_content)
+
+    def test__assert_login_view(self):
+        """Verifies that login view can be accessed as expected."""
+        with self.subTest('Check views without login'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_login_check', auto_login=False)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using super user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_login_check', user=self.test_superuser)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using admin user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_login_check', user=self.test_admin)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using inactive user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_login_check', user=self.test_inactive_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_login_check', user=self.test_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using new user'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_login_check', user=new_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Login Check', page_content)
+            self.assertIn('This view should require user login to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+    def test__assert_permission_view(self):
+        """Verifies that permission view can be accessed as expected."""
+
+        # Create required permission.
+        content_type = ContentType.objects.get_for_model(get_user_model())
+        test_permission = Permission.objects.create(
+            content_type=content_type,
+            codename='test_permission',
+            name='Test Permission',
+        )
+
+        with self.subTest('Check views without login'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', auto_login=False)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using super user - Without correct permission'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_superuser)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using super user - With correct permission'):
+            # Add permission to user.
+            self.test_superuser.user_permissions.add(test_permission)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_superuser)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using admin user - Without correct permission'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_admin)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using admin user - With correct permission'):
+            # Add permission to user.
+            self.test_admin.user_permissions.add(test_permission)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_admin)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using inactive user - Without correct permission'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_inactive_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using inactive user - With correct permission'):
+            # Add permission to user.
+            self.test_inactive_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_inactive_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - Without correct permission'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - With correct permission'):
+            # Add permission to user.
+            self.test_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=self.test_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using new user - Without correct permission'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=new_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using new user - With correct permission'):
+            # Add permission to user.
+            new_user.user_permissions.add(test_permission)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_permission_check', user=new_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Permission Check', page_content)
+            self.assertIn('This view should require permission of "test_permission" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+    def test__assert_group_view(self):
+        """Verifies that group view can be accessed as expected."""
+
+        # Create required group.
+        test_group = Group.objects.create(name='test_group')
+
+        with self.subTest('Check views without login'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', auto_login=False)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using super user - Without correct group'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_superuser)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using super user - With correct group'):
+            # Add group to user.
+            self.test_superuser.groups.add(test_group)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_superuser)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using admin user - Without correct group'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_admin)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertNotIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertNotIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using admin user - With correct group'):
+            # Add group to user.
+            self.test_admin.groups.add(test_group)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_admin)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using inactive user - Without correct group'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_inactive_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using inactive user - With correct group'):
+            # Add group to user.
+            self.test_inactive_user.groups.add(test_group)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_inactive_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertIn('Please login to see this page.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - Without correct group'):
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertNotIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertNotIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using standard user - With correct group'):
+            # Add group to user.
+            self.test_user.groups.add(test_group)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=self.test_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
+
+        with self.subTest('Check views using new user - Without correct group'):
+            # Generate user model.
+            new_user = get_user_model().objects.create(
+                username='new_user',
+                first_name='TestFirst',
+                last_name='TestLast',
+            )
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=new_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Login Page', page_content)
+            self.assertIn('You need to login to continue.', page_content)
+            self.assertNotIn('Please login to see this page.', page_content)
+            self.assertNotIn('Your account doesn\'t have access to this page. To proceed,', page_content)
+            self.assertNotIn('please login with an account that has access.', page_content)
+            self.assertIn('Username:', page_content)
+            self.assertIn('Password:', page_content)
+            self.assertIn('login', page_content)
+
+        with self.subTest('Check views using new user - With correct group'):
+            # Add group to user.
+            new_user.groups.add(test_group)
+
+            # Get response object.
+            response = self.assertGetResponse('test_app:view_with_group_check', user=new_user)
+
+            # Display debug data to console on test failure.
+            self.debug_data(response)
+
+            # Various checks to ensure page is the one we expect.
+            page_content = response.content.decode('utf-8')
+            self.assertIn('Django LTS v4.2 - Test Group Check', page_content)
+            self.assertIn('This view should require group of "test_group" to see.', page_content)
+            self.assertIn('Back to Test App Views', page_content)
diff --git a/django_v5/test_app/urls.py b/django_v5/test_app/urls.py
new file mode 100644
index 0000000..739c603
--- /dev/null
+++ b/django_v5/test_app/urls.py
@@ -0,0 +1,28 @@
+"""
+Urls for Django v4.2 test project app.
+"""
+
+# Third-Party Imports.
+from django.urls import path
+
+# Internal Imports.
+from . import views
+
+
+app_name = 'test_app'
+urlpatterns = [
+    # Test views that have various login/permission requirements.
+    path('view_with_login_check/', views.view_with_login_check, name='view_with_login_check'),
+    path('view_with_permission_check/', views.view_with_permission_check, name='view_with_permission_check'),
+    path('view_with_group_check/', views.view_with_group_check, name='view_with_group_check'),
+
+    # Test API views.
+    path('api/parse/', views.api_parse, name='api_parse'),
+    path('api/display/', views.api_display, name='api_display'),
+    path('api/send/', views.api_send, name='api_send'),
+
+    # Test app root, but as a class.
+    path('as_class', views.ExampleClassView.as_view(), name='index_as_class'),
+    # App root.
+    path('', views.index, name='index')
+]
diff --git a/django_v5/test_app/views.py b/django_v5/test_app/views.py
new file mode 100644
index 0000000..39b125f
--- /dev/null
+++ b/django_v5/test_app/views.py
@@ -0,0 +1,559 @@
+"""
+Views for Django v4.2 test project app.
+"""
+
+# System Imports.
+import json
+import html
+import re
+import requests
+
+# Third-Party Imports.
+from django.contrib.auth.decorators import login_required, permission_required
+from django.http import JsonResponse, QueryDict
+from django.views.decorators.csrf import csrf_exempt
+from django.views.decorators.http import require_http_methods
+from django.views.generic import TemplateView
+from django.shortcuts import redirect, render, reverse
+
+# Internal Imports.
+from test_app.forms import ApiSendForm
+from test_app.models import ApiRequestJson
+
+
+# region Index/Root Views
+
+def root_project_home_page(request):
+    """Shown for root page of entire project."""
+    return render(request, 'test_app/root_project_home_page.html')
+
+
+def index(request):
+    """Test app index page."""
+    return render(request, 'test_app/index.html')
+
+
+class ExampleClassView(TemplateView):
+    """A basic Class Django view,
+    with some of the more common built-in methods and documentation of what they do.
+
+    Note: Some of these methods won't do anything with TemplateView. For example,
+          the form valid/invalid methods require a class that will POST form data.
+          Such as CreateView or UpdateView.
+    """
+
+    # Magic DjangoView args. Often times, can just define these and skip most method calls.
+
+    # Template to render.
+    template_name = 'test_app/index.html'
+
+    # Url to use if redirecting.
+    # If args/kwargs are needed, then probably need to use get_redirect_url() instead.
+    url = None
+
+    # If using a ModelView (ListView, DetailView, etc), these define what model data to call with.
+    model = None
+    queryset = None     # Can call more complicated query logic in get_queryset().
+
+    # If using a ListView, this determines the number of results to display per page with pagination.
+    paginate_by = 25
+
+    # Params for views with form logic.
+    form_class = None       # Form class to use.
+    initial = {}            # Initial data to populate into form, if applicable.
+    success_url = None      # If args/kwargs are needed, then probably need to use get_success_url() instead.
+
+    def dispatch(self, request, *args, **kwargs):
+        """Determines initial logic to call on view access.
+        This is one of the first methods called by Django class views.
+        This determines which of [GET(), POST(), etc] base class handling methods are called.
+        If you need redirecting or other logic prior to calling these, do it here.
+
+        If not redirecting outside of this class, then should probably always finish
+        this function by returning a call to the original dispatch method.
+        """
+        return super().dispatch(request, *args, **kwargs)
+
+    def get_context_data(self, **kwargs):
+        """Pulls additional context data to be used in the template."""
+
+        # Get base context object.
+        context = super().get_context_data(**kwargs)
+
+        # Add new value to context.
+        context['is_class_view'] = True
+
+        # Return context.
+        return context
+
+    def get_queryset(self):
+        """If using a view that uses models (DetailView, ListView, etc), then this modifies the default queryset."""
+        queryset = super().get_queryset()
+
+        # Use additional model query logic here.
+
+        # Return our modified queryset.
+        return queryset
+
+    def get_ordering(self):
+        """Return the field or fields to use for ordering the queryset."""
+
+        # Replace this with a return to a single model field or list of model fields to order by.
+        return super().get_ordering()
+
+    def get(self, request, *args, **kwargs):
+        """Handling for GET response type."""
+
+        # Replace this with a response object.
+        return super().get(request, *args, **kwargs)
+
+    def post(self, request, *args, **kwargs):
+        """Handling for POST response type."""
+
+        # Replace this with either a response object, or a call to
+        # form_valid()/form_invalid() functions. Depending on what you need for class logic.
+        return super().post(request, *args, **kwargs)
+
+    def form_valid(self, form):
+        """When processing a form, this is the logic to run on form validation success."""
+
+        # Call parent logic. Should always include this line, as default views sometimes do additional processing.
+        response = super().form_valid(form)
+
+        # Do some handling with response here.
+
+        # Return some response object for render.
+        return response
+
+    def form_invalid(self, form):
+        """When processing a form, this is the logic to run on form validation failure."""
+
+        # Call parent logic. Should always include this line, as default views sometimes do additional processing.
+        response = super().form_invalid(form)
+
+        # Do some handling with response here.
+
+        # Return some response object for render.
+        return response
+
+    def get_success_url(self):
+        """When processing a form, determines how to get the url for form success redirect."""
+
+        # Replace this with a `reverse()` call to generate the correct URL.
+        return super().get_success_url()
+
+    def get_redirect_url(self, *args, **kwargs):
+        """When handling a redirect view, this determines how to get the url."""
+
+        # Replace this with a `reverse()` call to generate the correct URL.
+        return super().get_redirect_url()
+
+# endregion Index/Root Views
+
+
+# region Login/Permission Test Views
+
+@login_required
+def view_with_login_check(request):
+    """Test view with basic login check."""
+    return render(request, 'test_app/login_check.html')
+
+
+@permission_required('test_app.test_permission')
+def view_with_permission_check(request):
+    """Test view with basic User permission check."""
+    return render(request, 'test_app/permission_check.html')
+
+
+@login_required
+def view_with_group_check(request):
+    """Test view with basic User group check."""
+
+    # Check group.
+    user_groups = request.user.groups.all().values_list('name', flat=True)
+    if 'test_group' not in user_groups and not request.user.is_superuser:
+        return redirect(reverse('login'))
+
+    return render(request, 'test_app/group_check.html')
+
+# endregion Login/Permission Test Views
+
+
+# region API Views
+
+@csrf_exempt
+@require_http_methods(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
+def api_parse(request):
+    """Takes in JSON ping, and saves incoming value to web cookies.
+
+    Then if api_display view is called after, will display the saved cookie value to web page.
+
+    Allows quick debugging to make sure the expected, correct data is being sent.
+    """
+    print('')
+    print('api_parse():')
+
+    # Get data from response.
+    get_data = {}
+    post_data = {}
+    body_data = {}
+    header_data = {}
+    if request.headers:
+        print('Received HEADERS:')
+        header_data = dict(request.headers)
+        print(header_data)
+    if request.GET:
+        print('Received GET:')
+        get_data = _recurisive_json_parse(request.GET)
+        for key, value in get_data.items():
+            get_data[key] = value[0]
+        print(get_data)
+    if request.POST:
+        print('Received POST:')
+        post_data = _recurisive_json_parse(dict(request.POST))
+        print(post_data)
+    if request.body:
+        print('Received BODY:')
+        # Attempt to escape. Limited functionality so may not work.
+        # To be precise, functions well with a standard JSON response.
+        # But with any other response type that has a body, might break and be ugly.
+        body_data = _recurisive_json_parse(html.unescape(request.body.decode('UTF-8')))
+        print(body_data)
+    print('\n')
+
+    # Combine data.
+    data = {}
+    if header_data:
+        data['HEADERS'] = header_data
+    if get_data:
+        data['GET'] = get_data
+    if post_data:
+        data['POST'] = post_data
+    if body_data:
+        data['body'] = body_data
+    if not data:
+        data = {'data': 'No data found in request.'}
+
+    # Save api data to database.
+    model_instance = ApiRequestJson.objects.first()
+    if not model_instance:
+        model_instance = ApiRequestJson.objects.create()
+    model_instance.json_value = data
+    model_instance.save()
+
+    # Generate response.
+    return JsonResponse({'success': True})
+
+
+def _recurisive_json_parse(data_item):
+    """Helper function to ensure all sub-items of response are properly read-in."""
+
+    # Convert from potentially problematic types, for easier handling.
+    if isinstance(data_item, QueryDict):
+        data_item = dict(data_item)
+    if isinstance(data_item, tuple):
+        data_item = list(data_item)
+
+    # Process some known types.
+    if isinstance(data_item, dict):
+        # Is dictionary. Iterate over each (key, value) pair and attempt to convert.
+        for key, value in data_item.items():
+            data_item[key] = _recurisive_json_parse(value)
+
+    elif isinstance(data_item, list):
+        # Is iterable. Iterate over each item and attempt to convert.
+        for index in range(len(data_item)):
+            sub_item = data_item[index]
+            data_item[index] = _recurisive_json_parse(sub_item)
+
+    else:
+        # For all other types, just attempt naive conversion
+        try:
+            data_item = json.loads(data_item)
+        except:
+            # On any failure, just skip. Leave item as-is.
+            pass
+
+    # Return parsed data.
+    return data_item
+
+
+def api_display(request):
+    """After a JSON ping to api_parse view, this displays parsed value to web page.
+
+    Allows quick debugging to make sure the expected, correct data is being sent.
+    """
+
+    # Grab api data from session, if any.
+    model_instance = ApiRequestJson.objects.first()
+    if model_instance:
+        content = {
+            'payload_data': model_instance.json_value,
+            'payload_sent_at': model_instance.date_created,
+        }
+    else:
+        content = {
+            'payload_data': {},
+            'payload_sent_at': 'N/A',
+        }
+
+    # Attempt to output api data to browser.
+    response = JsonResponse(content, safe=False)
+
+    # Delete all existing instances of saved API data.
+    ApiRequestJson.objects.all().delete()
+
+    # Return data view to user.
+    return response
+
+
+def api_send(request):
+    """Test app index page."""
+    print('\n')
+    print('api_send():')
+
+    response_success = {}
+    response_error = {}
+    sent_data = {}
+
+    # Initialize formset.
+    form = ApiSendForm()
+
+    # Check if POST.
+    if request.POST:
+        # Is POST. Process data.
+        print('Is POST submission.')
+        has_error = False
+
+        post_data = request.POST
+        form = ApiSendForm(data=post_data)
+
+        if form.is_valid():
+            # Handle for form submission.
+            print('Submitted form data:')
+            print('{0}'.format(form.cleaned_data))
+
+            send_type = ''
+            if 'submit_get' in post_data:
+                send_type = 'GET'
+                # data.pop('submit_get')
+            if 'submit_post' in post_data:
+                send_type = 'POST'
+                # data.pop('submit_post')
+            if 'submit_put' in post_data:
+                send_type = 'PUT'
+                # data.pop('submit_put')
+            if 'submit_patch' in post_data:
+                send_type = 'PATCH'
+                # data.pop('submit_patch')
+            if 'submit_delete' in post_data:
+                send_type = 'DELETE'
+                # data.pop('submit_delete')
+
+            url = str(form.cleaned_data['url']).strip()
+            get_params = str(form.cleaned_data.get('get_params', '')).strip()
+            header_token = str(form.cleaned_data.get('header_token', '')).strip()
+            payload = str(form.cleaned_data.get('payload', '{}')).strip()
+            if len(payload) > 0:
+                try:
+                    payload = json.loads(payload)
+                except json.decoder.JSONDecodeError:
+                    has_error = True
+                    payload = {}
+                    form.add_error(
+                        'payload',
+                        'Unrecognized/invalid JSON syntax. Please double check syntax and try again.',
+                    )
+            else:
+                has_error = True
+                form.add_error(
+                    'payload',
+                    'Please provide JSON data to send. If API query is meant to be empty, use {}.',
+                )
+
+            # Determine url.
+            if get_params and len(get_params) > 0:
+                if url[-1] != '?' and get_params[0] != '?':
+                    url += '?'
+                url += get_params
+
+            # Determine header values.
+            headers = {'Accept': 'application/json'}
+            if header_token:
+                headers['token'] = header_token
+
+            # Determine data values.
+            if payload:
+                data = json.dumps(payload)
+            else:
+                data = json.dumps({'success': True})
+
+            # Generate API send object.
+            try:
+                # Generate based on clicked send button.
+                if not has_error and send_type == 'GET':
+                    response = requests.get(
+                        url,
+                        headers=headers,
+                        data=data,
+                        timeout=5,
+                    )
+
+                elif not has_error and send_type == 'POST':
+                    response = requests.post(
+                        url,
+                        headers=headers,
+                        data=data,
+                        timeout=5,
+                    )
+
+                elif not has_error and send_type == 'PUT':
+                    response = requests.put(
+                        url,
+                        headers=headers,
+                        data=data,
+                        timeout=5,
+                    )
+
+                elif not has_error and send_type == 'PATCH':
+                    response = requests.patch(
+                        url,
+                        headers=headers,
+                        data=data,
+                        timeout=5,
+                    )
+
+                elif not has_error and send_type == 'DELETE':
+                    response = requests.delete(
+                        url,
+                        headers=headers,
+                        data=data,
+                        timeout=5,
+                    )
+
+                elif not has_error:
+                    # Unknown send type. Somehow. Raise error.
+                    form.add_error(None, 'Invalid send_type. Was "{0}".'.format(send_type))
+            except Exception as err:
+                has_error = True
+                response_error['query_sent'] = False if not err.response else True
+                response_error['message'] = str(err.message) if hasattr(err, 'message') else str(err)
+                if 'Max retries exceeded with url' in response_error['message']:
+                    response_error['help_text'] = (
+                        'This error is often the result of a typo in the URL, or the desired endpoint being down. '
+                        'Are you sure you entered the destination URL correctly?'
+                    )
+
+            if not has_error:
+                # Handle for success state.
+
+                # Display sent input data to user.
+                # That way they can change the form for a subsequent request and still see what was sent last time.
+                sent_data['send_type'] = send_type
+                sent_data['url'] = url
+                sent_data['headers'] = headers
+                sent_data['content'] = data
+
+                # Parse returned response status code.
+                response_success['status'] = response.status_code
+                if response_success['status'] >= 400:
+                    # Define help_text key now to preserve location in display ordering.
+
+                    # Provide help text for some common error statuses.
+                    if response_success['status'] == 400:
+                        # 400: Bad Request
+                        response_success['help_text'] = (
+                            '400: Bad Request - This error is often the result of a bad or malformed request, such '
+                            'as incorrect or unexpected syntax. Double check that the sent request data is correct.'
+                        )
+                    elif response_success['status'] == 401:
+                        # 401: Unauthorized
+                        response_success['help_text'] = (
+                            '401: Unauthorized - This error is often the result of invalid or missing authentication '
+                            'credentials. Are you sure the authentication tokens are correctly provided?'
+                        )
+                    elif response_success['status'] == 403:
+                        # 403: Forbidden
+                        response_success['help_text'] = (
+                            '403: Forbidden - This error is often the result of invalid or missing authentication '
+                            'credentials. Are you sure the authentication tokens are correctly provided?'
+                        )
+                    elif response_success['status'] == 404:
+                        # 404: Not Found
+                        response_success['help_text'] = (
+                            '404: Not Found - This error is often the result of the requested url not existing on the '
+                            'server. Are you sure you entered the destination URL correctly?'
+                        )
+                    elif response_success['status'] == 405:
+                        # 405: Method Not Allowed
+                        response_success['help_text'] = (
+                            '405: Method Not Allowed - This error is often the result of the destination understanding '
+                            'the sent response type (GET/POST/PUT/PATCH/DELETE), but not supporting said type. '
+                            'If this is a server you have access to, then double check that the endpoint is configured '
+                            'correctly.'
+                        )
+                    elif response_success['status'] == 415:
+                        # 415: Unsupported Media Type
+                        response_success['help_text'] = (
+                            '415: Unsupported Media Type - This error is often the result of the destination '
+                            'being unable to parse the provided content. Are you sure the payload was entered '
+                            'correctly?'
+                        )
+                    elif response_success['status'] == 500:
+                        # 500: Server Error
+                        response_success['help_text'] = (
+                            '500: Server Error - This error is often the result of your request being received, but '
+                            'the server broke when trying to process the request. If this is a server you have '
+                            'access to, then double check the server logs for more details.'
+                        )
+
+                # Parse returned response header data.
+                if response.headers:
+                    response_success['headers'] = response.headers
+
+                # Parse returned response content.
+                if response.headers['content-Type'] and response.headers['Content-Type'] == 'application/json':
+                    response_success['content'] = response.json()
+                else:
+                    content = html.unescape(response.content.decode('UTF-8'))
+
+                    # NOTE: Below copied from Django ExpandedTestCase package.
+                    # Replace html linebreak with actual newline character.
+                    content = re.sub('<br>|</br>|<br/>|<br />', '\n', content)
+
+                    # Replace non-breaking space with actual space character.
+                    content = re.sub('(&nbsp;)+', ' ', content)
+
+                    # Replace any carriage return characters with newline character.
+                    content = re.sub(r'\r+', '\n', content)
+
+                    # Replace any whitespace trapped between newline characters.
+                    # This is empty/dead space, likely generated by how Django handles templating.
+                    content = re.sub(r'\n\s+\n', '\n', content)
+
+                    # Replace any repeating linebreaks.
+                    content = re.sub(r'\n\n+', '\n', content)
+
+                    # # Reduce any repeating whitespace instances.
+                    # content = re.sub(r' ( )+', ' ', content)
+
+                    # Strip final calculated string of extra outer whitespace.
+                    content = str(content).strip()
+                    response_success['content'] = content
+
+                # Handle if was response was received, but it gave error level status.
+                if response_success['status'] >= 400:
+                    response_error = response_success
+                    response_success = {}
+
+    print('Rendering response...')
+    print('')
+
+    return render(request, 'test_app/api_send.html', {
+        'form': form,
+        'sent_data': sent_data,
+        'response_success': response_success,
+        'response_error': response_error,
+    })
+
+# endregion API Views
-- 
GitLab