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('( )+', ' ', 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