From 65087eb5e821b1c6b573ca3fb08f713dc941e97f Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Fri, 11 Oct 2024 22:47:57 +0330 Subject: [PATCH 1/4] :zap::sparkles: feat(settings): Add include_log_iboard config - Added default for include_log_iboard setting - Added config for new setting - Updated checks for adding validation for new bool setting --- django_logging/constants/default_settings.py | 1 + django_logging/settings/checks.py | 4 ++++ django_logging/utils/get_conf.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+) diff --git a/django_logging/constants/default_settings.py b/django_logging/constants/default_settings.py index b38a036..8bd40c8 100644 --- a/django_logging/constants/default_settings.py +++ b/django_logging/constants/default_settings.py @@ -27,6 +27,7 @@ class DefaultLoggingSettings: auto_initialization_enable: bool = True initialization_message_enable: bool = True log_sql_queries_enable: bool = False + include_log_iboard: bool = False log_file_formats: LogFileFormats = field( default_factory=lambda: cast( LogFileFormats, diff --git a/django_logging/settings/checks.py b/django_logging/settings/checks.py index adc4856..0f0c563 100644 --- a/django_logging/settings/checks.py +++ b/django_logging/settings/checks.py @@ -6,6 +6,7 @@ from django_logging.utils.get_conf import ( get_config, get_log_dir_size_limit, + include_log_iboard, is_auto_initialization_enabled, is_initialization_message_enabled, is_log_sql_queries_enabled, @@ -156,6 +157,9 @@ def check_logging_settings(app_configs: Dict[str, Any], **kwargs: Any) -> List[E log_date_format = log_settings.get("log_date_format") errors.extend(validate_date_format(log_date_format, "LOG_DATE_FORMAT")) # type: ignore + # Validate INCLUDE_LOG_iBOARD + errors.extend(validate_boolean_setting(include_log_iboard(), "INCLUDE_LOG_iBOARD")) + # Validate AUTO_INITIALIZATION_ENABLE errors.extend( validate_boolean_setting( diff --git a/django_logging/utils/get_conf.py b/django_logging/utils/get_conf.py index 3e2b014..aa59f6e 100644 --- a/django_logging/utils/get_conf.py +++ b/django_logging/utils/get_conf.py @@ -157,3 +157,18 @@ def get_log_dir_size_limit() -> int: defaults = DefaultLoggingSettings() return log_settings.get("LOG_DIR_SIZE_LIMIT", defaults.log_dir_size_limit) + + +def include_log_iboard() -> bool: + """Check if the INCLUDE_LOG_iBOARD for the logging system is set to True in + Django settings. + + Returns: + bool: True if INCLUDE_LOG_iBOARD, False otherwise. + Defaults to False if not specified. + + """ + log_settings = getattr(settings, "DJANGO_LOGGING", {}) + defaults = DefaultLoggingSettings() + + return log_settings.get("INCLUDE_LOG_iBOARD", defaults.include_log_iboard) From f16ce9523695fc15404b8b3ba2d1ec92ff1f3eef Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Fri, 11 Oct 2024 23:09:05 +0330 Subject: [PATCH 2/4] :sparkles: feat(views): Add LogiBoardView to render LogiBoard for superusers and return 403 for non-superusers - Implemented LogiBoardView using Django's TemplateView to display the log_iboard.html template. - Added custom logic in the get method to restrict access to the LogiBoard page to superusers only. - Non-superusers are shown an 'Access Denied' error response page with a 403 status code. - Included detailed docstrings and type annotations for clarity. --- django_logging/urls.py | 9 ++++++ django_logging/views/__init__.py | 0 django_logging/views/log_iboard.py | 44 ++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 django_logging/urls.py create mode 100644 django_logging/views/__init__.py create mode 100644 django_logging/views/log_iboard.py diff --git a/django_logging/urls.py b/django_logging/urls.py new file mode 100644 index 0000000..def100a --- /dev/null +++ b/django_logging/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from django_logging.utils.get_conf import include_log_iboard +from django_logging.views.log_iboard import LogiBoardView + +urlpatterns = [] + +if include_log_iboard(): + urlpatterns.append(path("log-iboard/", LogiBoardView.as_view(), name="log-iboard")) diff --git a/django_logging/views/__init__.py b/django_logging/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_logging/views/log_iboard.py b/django_logging/views/log_iboard.py new file mode 100644 index 0000000..06f57bc --- /dev/null +++ b/django_logging/views/log_iboard.py @@ -0,0 +1,44 @@ +from typing import Any, Dict + +from django.http import HttpRequest, HttpResponse +from django.shortcuts import render +from django.views.generic import TemplateView + + +class LogiBoardView(TemplateView): + """View to render the LogiBoard page for superusers. + + Non-superusers are denied access and shown an error response page + with a 403 status code. + + """ + + template_name = "log_iboard.html" + + def get( + self, request: HttpRequest, *args: Any, **kwargs: Dict[str, Any] + ) -> HttpResponse: + """Handles GET requests. Renders the LogiBoard page for superusers, + otherwise returns a 403 error response for non-superusers. + + Args: + request (HttpRequest): The HTTP request object. + *args (Any): Additional positional arguments. + **kwargs (Dict[str, Any]): Additional keyword arguments. + + Returns: + HttpResponse: The rendered LogiBoard page for superusers or an error response page for non-superusers. + + """ + if request.user.is_superuser: + return super().get(request, *args, **kwargs) + + return render( + request, + "error_response.html", + { + "title": "Access Denied", + "message": "You do not have permission to view this page.", + }, + status=403, + ) From 62e7f4499c1b731f90cc52faa122d0b43fafef30 Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Fri, 11 Oct 2024 23:28:00 +0330 Subject: [PATCH 3/4] :rotating_light::white_check_mark::heavy_check_mark: tests(views): Add tests for LogiBoardView to verify superuser access and handle 403 for non-superusers - Implemented a test suite for LogiBoardView to ensure: - Superusers can access the LogiBoard page and the correct template is rendered. - Non-superusers are denied access and receive a 403 Forbidden response. - Added checks for correct template rendering and appropriate response content for both superusers and non-superusers. - Utilized pytest with Django's testing client and test markers to ensure test execution compatibility with Python version requirements. - Enhanced test reliability by verifying response status code, template name, and error messages for non-superusers. Closes #117 --- django_logging/tests/conftest.py | 6 +- django_logging/tests/fixtures/__init__.py | 1 + .../tests/fixtures/views_fixture.py | 28 +++++++++ django_logging/tests/setup.py | 37 +++++++++++- django_logging/tests/views/__init__.py | 0 django_logging/tests/views/test_log_iboard.py | 59 +++++++++++++++++++ 6 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 django_logging/tests/fixtures/views_fixture.py create mode 100644 django_logging/tests/views/__init__.py create mode 100644 django_logging/tests/views/test_log_iboard.py diff --git a/django_logging/tests/conftest.py b/django_logging/tests/conftest.py index b0fc2ce..690b7da 100644 --- a/django_logging/tests/conftest.py +++ b/django_logging/tests/conftest.py @@ -1,3 +1,4 @@ +from django_logging.tests.setup import configure_django_settings from django_logging.tests.fixtures import ( admin_email_mock_settings, colored_formatter, @@ -24,7 +25,6 @@ temp_log_directory, temp_xml_log_directory, xml_formatter, + setup_users, + client ) -from django_logging.tests.setup import configure_django_settings - -configure_django_settings() diff --git a/django_logging/tests/fixtures/__init__.py b/django_logging/tests/fixtures/__init__.py index 72e664e..4f5c234 100644 --- a/django_logging/tests/fixtures/__init__.py +++ b/django_logging/tests/fixtures/__init__.py @@ -21,3 +21,4 @@ request_middleware, ) from .settings_fixture import mock_settings, reset_settings +from .views_fixture import setup_users, client diff --git a/django_logging/tests/fixtures/views_fixture.py b/django_logging/tests/fixtures/views_fixture.py new file mode 100644 index 0000000..53f6e03 --- /dev/null +++ b/django_logging/tests/fixtures/views_fixture.py @@ -0,0 +1,28 @@ +from typing import Dict + +import pytest +from django.contrib.auth.models import User +from django.test import Client + + +@pytest.fixture +def setup_users(db) -> Dict[str, User]: + """ + Fixture to create a superuser and a normal user for testing purposes. + Returns a dictionary with `superuser` and `non_superuser` keys. + """ + superuser = User.objects.create_superuser( + username="admin", password="adminpassword", email="admin@example.com" + ) + non_superuser = User.objects.create_user( + username="user", password="userpassword", email="user@example.com" + ) + return {"superuser": superuser, "non_superuser": non_superuser} + + +@pytest.fixture +def client() -> Client: + """ + Fixture to provide a test client. + """ + return Client() diff --git a/django_logging/tests/setup.py b/django_logging/tests/setup.py index d0e8d54..e780317 100644 --- a/django_logging/tests/setup.py +++ b/django_logging/tests/setup.py @@ -1,11 +1,28 @@ import django from django.conf import settings +import string +import random + + +def generate_secret_key(length: int = 50) -> str: + """ + Generates a random secret key for Django settings. + + Args: + length (int): The length of the secret key. Default is 50 characters. + + Returns: + str: A randomly generated secret key. + """ + characters = string.ascii_letters + string.digits + string.punctuation + return "".join(random.choice(characters) for _ in range(length)) def configure_django_settings() -> None: if not settings.configured: settings.configure( DEBUG=True, + SECRET_KEY=generate_secret_key(), # Add a secret key for testing DATABASES={ "default": { "ENGINE": "django.db.backends.sqlite3", @@ -13,11 +30,22 @@ def configure_django_settings() -> None: } }, INSTALLED_APPS=[ - "django.contrib.contenttypes", "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", "django_logging", ], - MIDDLEWARE=[], + 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", + ], TEMPLATES=[ { "BACKEND": "django.template.backends.django.DjangoTemplates", @@ -34,6 +62,7 @@ def configure_django_settings() -> None: }, ], DJANGO_LOGGING={ + "INCLUDE_LOG_iBOARD": True, "AUTO_INITIALIZATION_ENABLE": True, "INITIALIZATION_MESSAGE_ENABLE": True, "LOG_FILE_LEVELS": ["DEBUG", "INFO"], @@ -72,5 +101,9 @@ def configure_django_settings() -> None: TIME_ZONE="UTC", USE_I18N=True, USE_TZ=True, + ROOT_URLCONF="django_logging.urls", + STATIC_URL="static/" ) django.setup() + +configure_django_settings() \ No newline at end of file diff --git a/django_logging/tests/views/__init__.py b/django_logging/tests/views/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_logging/tests/views/test_log_iboard.py b/django_logging/tests/views/test_log_iboard.py new file mode 100644 index 0000000..84e9e4a --- /dev/null +++ b/django_logging/tests/views/test_log_iboard.py @@ -0,0 +1,59 @@ +import sys + +import pytest +from django.urls import reverse +from django.test import Client +from django.contrib.auth.models import User +from typing import Dict + +from django_logging.tests.constants import PYTHON_VERSION, PYTHON_VERSION_REASON + +pytestmark = [ + pytest.mark.views, + pytest.mark.views_log_iboard, + pytest.mark.skipif(sys.version_info < PYTHON_VERSION, reason=PYTHON_VERSION_REASON), +] + + +@pytest.mark.django_db +class TestLogiBoardView: + """ + Test suite for the `LogiBoardView` class-based view. + + This test suite covers: + - Access control for superuser and non-superuser. + - Rendering the correct template for superuser. + - Correct response and content type for non-superuser. + + Methods: + - test_superuser_access: Ensures superusers can access the LogiBoard page. + - test_non_superuser_access: Ensures non-superusers are forbidden from accessing the LogiBoard page. + """ + + def test_superuser_access( + self, client: Client, setup_users: Dict[str, User] + ) -> None: + """ + Test that a superuser can access the `LogiBoardView` and the correct template is rendered. + """ + client.login(username="admin", password="adminpassword") + response = client.get(reverse("log-iboard")) + assert response.status_code == 200, "Superuser should have access to the page." + assert ( + "log_iboard.html" in response.template_name + ), "Should render the correct template for superuser." + + def test_non_superuser_access( + self, client: Client, setup_users: Dict[str, User] + ) -> None: + """ + Test that a non-superuser receives a 403 Forbidden response when accessing the `LogiBoardView`. + """ + client.login(username="user", password="userpassword") + response = client.get(reverse("log-iboard")) + assert ( + response.status_code == 403 + ), "Non-superuser should not have access to the page." + assert ( + "text/html" in response["Content-Type"] + ), "'text/html' should be in Content type for forbidden access." From e21d839b454c4462988d8ecb57d23a0ecdf44cc6 Mon Sep 17 00:00:00 2001 From: MEHRSHAD MIRSHEKARY Date: Fri, 11 Oct 2024 23:31:41 +0330 Subject: [PATCH 4/4] :zap::wrench: chore(pyproject): Add pytest new markers --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 6d6c811..625695e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -203,6 +203,8 @@ markers = [ "decorators_execution_tracking: Tests specifically for the execution_tracking module.", "contextvar: Tests for context variable handling in the logging package.", "contextvar_manager: Tests for managing context variables across different log contexts.", + "views: Tests for MVT views in the django logging package.", + "views_log_iboard: Tests specially for the log_iboard view.", ] asyncio_default_fixture_loop_scope = [ "function" ]