diff --git a/collector/conftest.py b/collector/conftest.py index fadbcd5..a925f11 100644 --- a/collector/conftest.py +++ b/collector/conftest.py @@ -1,7 +1,3 @@ -import random -import string -from typing import Callable - import pytest from django.test import Client @@ -14,12 +10,3 @@ def enable_db_access_for_all_tests(db): @pytest.fixture(scope="session", name="session") def fixture_longlived_client() -> Client: return Client() - - -@pytest.fixture(name="random_name", scope="session") -def fixture_random_name_generator() -> Callable[[int], str]: - def name_generator(length: int = 7) -> str: - name = "".join(random.choices(string.ascii_letters, k=length)) - return name - - return name_generator diff --git a/collector/tests.py b/collector/tests.py index 69eba73..9c2c77f 100644 --- a/collector/tests.py +++ b/collector/tests.py @@ -1,21 +1,20 @@ -from typing import Callable import pytest from django.test import Client from django.urls import reverse from hypothesis import given, strategies as st -from logot import Logot, logged from .models import Teil names = st.text(alphabet=st.characters(exclude_categories=["C"]), min_size=1) - @given(data=names) def test_submitted_data_ends_up_in_database(data, session: Client): + with pytest.raises(Teil.DoesNotExist): Teil.objects.get(name=data) response = session.post(reverse("collector:enter"), data={"new_name": data}) + assert response.status_code == 302 assert Teil.objects.get(name=data) @@ -26,46 +25,11 @@ def test_entering_same_name_twice_does_not_change_database_entry(data, session: response = session.post(reverse("collector:enter"), data={"new_name": data}) assert response.status_code == 302 + assert Teil.objects.filter(name=data).count() == 1 response = session.post(reverse("collector:enter"), data={"new_name": data}) assert response.status_code == 302 + assert Teil.objects.filter(name=data).count() == 1 - -@pytest.mark.parametrize( - "http_method,expected_status", - [ - ("GET", 405), - ("PATCH", 405), - ("POST", 302), - ("PUT", 405), - ], -) -def test_enter_endpoint_accepts_only_post_requests( - client: Client, - http_method: str, - expected_status: int, - random_name: Callable[[int], str], -): - client_method = getattr(client, http_method.lower()) - - response = client_method( - reverse("collector:enter"), data={"new_name": random_name(8)} - ) - assert response.status_code == expected_status - - -@pytest.mark.xfail("WIP, kein Plan warum nichts gecaptured zu werden scheint") -def test_enter_endpoints_emits_expected_logs( - client: Client, logot: Logot, random_name: Callable[[int], str] -): - same_name = random_name(10) - - response = client.post(reverse("collector:enter"), data={"new_name": same_name}) - assert response.status_code == 302 - logot.assert_logged(logged.info("New Teil entered")) - - response = client.post(reverse("collector:enter"), data={"new_name": same_name}) - assert response.status_code == 302 - logot.assert_logged(logged.warning("Teil already existed")) diff --git a/collector/views.py b/collector/views.py index a273c68..cc6023d 100644 --- a/collector/views.py +++ b/collector/views.py @@ -1,4 +1,4 @@ -import structlog +import logging from typing import Any from django.db import transaction @@ -6,11 +6,10 @@ from django.db.models import QuerySet from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.urls import reverse from django.views import generic -from django.views.decorators.http import require_http_methods from .models import Teil -logger = structlog.get_logger(__name__) +logger = logging.getLogger(__name__) DEFAULT_TEILE_NUMBER = 10 @@ -45,16 +44,11 @@ class DetailView(generic.DetailView): return context -@require_http_methods(["POST"]) def enter(request: HttpRequest) -> HttpResponse: try: - # if .create() failed, the transaction is rolled back and we are in a - # clean (database) state to handle exceptions with transaction.atomic(): - teil = Teil.objects.create(name=request.POST["new_name"]) + Teil.objects.create(name=request.POST["new_name"]) except Exception: logger.warning("Teil already existed") - else: - logger.info("New Teil entered", teil_id=teil.pk) return HttpResponseRedirect(reverse("collector:list")) diff --git a/config/settings.py b/config/settings.py index febcbb7..bde6d3d 100644 --- a/config/settings.py +++ b/config/settings.py @@ -8,17 +8,14 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/5.1/ref/settings/ """ -import os -from pathlib import Path - import environ -import structlog +from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent env = environ.Env( DEBUG=(bool, False), - DEPLOYMENT=(str, "DEV"), + DEPLOYMENT=(str, "Dev"), ) env.read_env(env.str("ENV_PATH", BASE_DIR / ".env")) # pyright: ignore[reportArgumentType] @@ -26,7 +23,6 @@ env.read_env(env.str("ENV_PATH", BASE_DIR / ".env")) # pyright: ignore[reportAr SECRET_KEY = env("SECRET_KEY") DEBUG = env("DEBUG") -DEPLOYMENT = str(env("DEPLOYMENT")).upper() ALLOWED_HOSTS = [] SESSION_COOKIE_SECURE = True @@ -35,7 +31,6 @@ CSRF_COOKIE_SECURE = True # Application definition INSTALLED_APPS = [ - "django_structlog", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -44,7 +39,7 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", "collector.apps.CollectorConfig", ] -if DEPLOYMENT == "DEV": +if env("DEPLOYMENT") == "Dev": INSTALLED_APPS += [ "django_extensions", "debug_toolbar", @@ -52,7 +47,6 @@ if DEPLOYMENT == "DEV": SHELL_PLUS = "ipython" MIDDLEWARE = [ - "django_structlog.middlewares.RequestMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", @@ -61,7 +55,7 @@ MIDDLEWARE = [ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -if DEPLOYMENT == "DEV": +if env("DEPLOYMENT") == "Dev": MIDDLEWARE += [ "debug_toolbar.middleware.DebugToolbarMiddleware", ] @@ -95,6 +89,23 @@ DATABASES = { "default": env.db(), } +# Password validation +# https://docs.djangoproject.com/en/5.1/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/5.1/topics/i18n/ LANGUAGE_CODE = "en-us" @@ -109,50 +120,3 @@ STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" - -LOGGING = { - "version": 1, - "disable_existing_loggers": True, - "formatters": { - "logftm_formatter": { - "()": structlog.stdlib.ProcessorFormatter, - "processor": structlog.processors.LogfmtRenderer(sort_keys=True, key_order=["level", "event"]), - }, - }, - "handlers": { - "console": { - "class": "logging.StreamHandler", - "formatter": "logftm_formatter", - }, - }, - "loggers": { - "django_structlog": { - "handlers": ["console"], - "level": "DEBUG", - }, - "root": { - "handlers": ["console"], - "level": "DEBUG", - }, - }, -} - -structlog.configure( - processors=[ - structlog.contextvars.merge_contextvars, - structlog.stdlib.filter_by_level, - structlog.processors.TimeStamper(fmt="iso"), - structlog.stdlib.add_logger_name, - structlog.stdlib.add_log_level, - structlog.stdlib.PositionalArgumentsFormatter(), - structlog.processors.StackInfoRenderer(), - structlog.processors.format_exc_info, - structlog.processors.UnicodeDecoder(), - structlog.stdlib.ProcessorFormatter.wrap_for_formatter, - ], - logger_factory=structlog.stdlib.LoggerFactory(), - cache_logger_on_first_use=True, -) -os.makedirs(BASE_DIR / "logs", exist_ok=True) - -# DJANGO_STRUCTLOG_COMMAND_LOGGING_ENABLED = True diff --git a/config/urls.py b/config/urls.py index b02a46c..f42570b 100644 --- a/config/urls.py +++ b/config/urls.py @@ -20,7 +20,7 @@ from django.contrib import admin from django.urls import include, path urlpatterns = [ - path("", include("collector.urls")), + path("collector/", include("collector.urls")), path("admin/", admin.site.urls), ] diff --git a/pyproject.toml b/pyproject.toml index c60b9ff..63bbc12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ authors = [ ] [tool.pytest.ini_options] -pythonpath = ["."] +pythonpath = "." python_files = [ "test_*.py", "tests.py", @@ -23,7 +23,6 @@ addopts = [ "--no-migrations", "--capture=sys", ] -logot_capturer = "logot.structlog.StructlogCapturer" [build-system] requires = ["pdm-backend"] diff --git a/requirements/dev.in b/requirements/dev.in index 8c26e50..4b7cb9f 100644 --- a/requirements/dev.in +++ b/requirements/dev.in @@ -4,6 +4,5 @@ django-debug-toolbar django-stubs ipython pip-compile-multi -rich ruff uv diff --git a/requirements/dev.txt b/requirements/dev.txt index bd3692d..5b4dab8 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,4 +1,4 @@ -# SHA1:dabc24f4b9a41d5fae3314c9a05a98434dd126e3 +# SHA1:8a2089b98bf40ef6e9739b167a6c3bdc35a7c180 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -17,9 +17,7 @@ executing==2.2.0 ipython==9.0.2 ipython-pygments-lexers==1.1.1 jedi==0.19.2 -markdown-it-py==3.0.0 matplotlib-inline==0.1.7 -mdurl==0.1.2 parso==0.8.4 pexpect==4.9.0 pip==25.0.1 @@ -30,7 +28,6 @@ ptyprocess==0.7.0 pure-eval==0.2.3 pygments==2.19.1 pyproject-hooks==1.2.0 -rich==13.9.4 ruff==0.11.0 setuptools==76.0.0 stack-data==0.6.3 diff --git a/requirements/prod.in b/requirements/prod.in index 4058e05..c360703 100644 --- a/requirements/prod.in +++ b/requirements/prod.in @@ -1,6 +1,4 @@ django django-environ django-extensions -django-structlog[commands] psycopg[binary,pool] -structlog>24,<25 diff --git a/requirements/prod.txt b/requirements/prod.txt index 493a11f..19971fe 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -1,4 +1,4 @@ -# SHA1:48c3af154036b224a6b21f175ea0a949febaf6f5 +# SHA1:0534e06bf8de6836b424e914cfbef002223d6f48 # # This file is autogenerated by pip-compile-multi # To update, run: @@ -9,12 +9,8 @@ asgiref==3.8.1 django==5.1.7 django-environ==0.12.0 django-extensions==3.2.3 -django-ipware==7.0.1 -django-structlog==9.0.1 psycopg==3.2.6 psycopg-binary==3.2.6 psycopg-pool==3.2.6 -python-ipware==3.0.0 sqlparse==0.5.3 -structlog==24.4.0 typing-extensions==4.12.2 diff --git a/requirements/test.in b/requirements/test.in index f1e3b52..2c801fa 100644 --- a/requirements/test.in +++ b/requirements/test.in @@ -3,4 +3,3 @@ hypothesis[django] pytest pytest-django -logot[pytest,structlog] diff --git a/requirements/test.txt b/requirements/test.txt index 7aa87fa..2f8d5ca 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,4 +1,4 @@ -# SHA1:40e4b481355fc16525f8944d39904264b6e382a6 +# SHA1:9bf147356ad56dbe073ef0973141bdfb94631c9b # # This file is autogenerated by pip-compile-multi # To update, run: @@ -7,9 +7,8 @@ # -r prod.txt attrs==25.3.0 -hypothesis==6.129.2 +hypothesis==6.129.1 iniconfig==2.0.0 -logot==1.3.0 packaging==24.2 pluggy==1.5.0 pytest==8.3.5