diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4e252e4..3c605ec 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,14 +40,28 @@ pip: - ./env/ expire_in: 30 mins +just: + stage: build + before_script: + - apt-get update && apt-get install -y curl + script: + - curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to $CI_PROJECT_DIR/.just-bin + artifacts: + name: 'pip-$CI_JOB_ID' + paths: + - $CI_PROJECT_DIR/.just-bin + expire_in: 30 mins + .python_test_template: &python_test_template stage: test dependencies: - pip - collect_static - static + - just before_script: - source env/bin/activate + - mv $CI_PROJECT_DIR/.just-bin/just /usr/local/bin/just variables: SECRET_KEY: super-secret @@ -57,9 +71,11 @@ collect_static: dependencies: - pip - static + - just needs: - pip - static + - just script: - ./manage.py collectstatic --noinput -v2 --clear artifacts: @@ -80,3 +96,8 @@ django_checks: script: - ./manage.py check - ./manage.py makemigrations --check --noinput + +python_lint: + <<: *python_test_template + script: + - just lint diff --git a/dev-requirements.txt b/dev-requirements.txt index 8c2b31b..024928a 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,7 @@ -r requirements.txt honcho==1.1.0 +django-stubs[compatible-mypy]==1.11.0 +flake8==4.0.1 +isort==5.10.1 +black==22.3.0 diff --git a/justfile b/justfile index 158b6f4..b5e9beb 100644 --- a/justfile +++ b/justfile @@ -21,5 +21,18 @@ install: @manage +ARGS: ./manage.py {{ ARGS }} +@pip +ARGS: + pip {{ ARGS }} + test *ARGS: ./manage.py test {{ ARGS }} + +format: + black website + isort website + +lint: + black --check website + isort --check website + flake8 website + mypy website diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..99883e6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,23 @@ +[mypy] +no_implicit_optional = True +warn_unused_ignores = True +strict_optional = True +check_untyped_defs = True +ignore_missing_imports = True +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +plugins = mypy_django_plugin.main + +[mypy.plugins.django-stubs] +django_settings_module = "website.settings" + +[isort] +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +line_length=88 + +[flake8] +extend_ignore=E128,E501 diff --git a/website/common/models.py b/website/common/models.py index 86d1057..52dcc3f 100644 --- a/website/common/models.py +++ b/website/common/models.py @@ -1,5 +1,3 @@ -from django.db import models - from wagtail.models import Page @@ -9,5 +7,5 @@ class BasePage(Page): @classmethod @property - def body_class(cls): + def body_class(cls) -> str: return "page-" + cls._meta.db_table.replace("_", "-") diff --git a/website/common/tests.py b/website/common/tests.py index 121a10b..8bbfa72 100644 --- a/website/common/tests.py +++ b/website/common/tests.py @@ -1,12 +1,17 @@ from django.test import SimpleTestCase -from .utils import get_page_models + from .models import BasePage +from .utils import get_page_models + class BasePageTestCase(SimpleTestCase): - def test_unique_body_classes(self): + def test_unique_body_classes(self) -> None: body_classes = [page.body_class for page in get_page_models()] self.assertEqual(len(body_classes), len(set(body_classes))) - def test_pages_inherit_base_page(self): + def test_pages_inherit_base_page(self) -> None: for page_model in get_page_models(): - self.assertTrue(issubclass(page_model, BasePage), f"{page_model} does not inherit from {BasePage}.") + self.assertTrue( + issubclass(page_model, BasePage), + f"{page_model} does not inherit from {BasePage}.", + ) diff --git a/website/common/utils.py b/website/common/utils.py index a97dc91..4758636 100644 --- a/website/common/utils.py +++ b/website/common/utils.py @@ -1,6 +1,10 @@ -from wagtail.models import get_page_models as get_wagtail_page_models, Page +from typing import Type -def get_page_models(): +from wagtail.models import Page +from wagtail.models import get_page_models as get_wagtail_page_models + + +def get_page_models() -> list[Type[Page]]: page_models = get_wagtail_page_models().copy() page_models.remove(Page) return page_models diff --git a/website/home/migrations/0002_create_homepage.py b/website/home/migrations/0002_create_homepage.py index ca328fa..b6cec3d 100644 --- a/website/home/migrations/0002_create_homepage.py +++ b/website/home/migrations/0002_create_homepage.py @@ -1,8 +1,11 @@ # -*- coding: utf-8 -*- from django.db import migrations +from django.db.backends.base.schema import BaseDatabaseSchemaEditor -def create_homepage(apps, schema_editor): +def create_homepage( + apps: migrations.state.StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: # Get models ContentType = apps.get_model("contenttypes.ContentType") Page = apps.get_model("wagtailcore.Page") @@ -34,7 +37,9 @@ def create_homepage(apps, schema_editor): Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True) -def remove_homepage(apps, schema_editor): +def remove_homepage( + apps: migrations.state.StateApps, schema_editor: BaseDatabaseSchemaEditor +) -> None: # Get models ContentType = apps.get_model("contenttypes.ContentType") HomePage = apps.get_model("home.HomePage") diff --git a/website/home/models.py b/website/home/models.py index 9b41a83..67bcf7c 100644 --- a/website/home/models.py +++ b/website/home/models.py @@ -1,4 +1,5 @@ from website.common.models import BasePage + class HomePage(BasePage): pass diff --git a/website/home/tests.py b/website/home/tests.py index b53ddaf..5fa03bb 100644 --- a/website/home/tests.py +++ b/website/home/tests.py @@ -1,11 +1,15 @@ from django.test import TestCase + from .models import HomePage + class HomePageTestCase(TestCase): + page: HomePage + @classmethod - def setUpTestData(cls): + def setUpTestData(cls) -> None: cls.page = HomePage.objects.get() - def test_accessible(self): - response = self.client.get(self.page.url) - self.assertEqual(response.status_code, 200) + def test_accessible(self) -> None: + response = self.client.get(self.page.url) + self.assertEqual(response.status_code, 200) diff --git a/website/search/views.py b/website/search/views.py index 703c4dd..3313e2a 100644 --- a/website/search/views.py +++ b/website/search/views.py @@ -1,11 +1,11 @@ from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator +from django.http.request import HttpRequest from django.template.response import TemplateResponse - from wagtail.models import Page from wagtail.search.models import Query -def search(request): +def search(request: HttpRequest) -> TemplateResponse: search_query = request.GET.get("query", None) page = request.GET.get("page", 1) diff --git a/website/settings.py b/website/settings.py index df066bc..da1eba6 100644 --- a/website/settings.py +++ b/website/settings.py @@ -1,19 +1,17 @@ from pathlib import Path + import environ BASE_DIR = Path(__file__).parent.parent -env = environ.Env( - DEBUG=(bool, False), - BASE_HOSTNAME=(str, "example.com") -) +env = environ.Env(DEBUG=(bool, False), BASE_HOSTNAME=(str, "example.com")) # Read local secrets -environ.Env.read_env(BASE_DIR / '.env') +environ.Env.read_env(BASE_DIR / ".env") -DEBUG = env('DEBUG') +DEBUG = env("DEBUG") -SECRET_KEY = env('SECRET_KEY') +SECRET_KEY = env("SECRET_KEY") ALLOWED_HOSTS = ["*"] @@ -80,9 +78,7 @@ TEMPLATES = [ WSGI_APPLICATION = "website.wsgi.application" -DATABASES = { - 'default': env.db(default=f"sqlite:///{BASE_DIR}/db.sqlite3") -} +DATABASES = {"default": env.db(default=f"sqlite:///{BASE_DIR}/db.sqlite3")} # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ @@ -132,5 +128,5 @@ WAGTAILSEARCH_BACKENDS = { } } -BASE_HOSTNAME = env('BASE_HOSTNAME') +BASE_HOSTNAME = env("BASE_HOSTNAME") WAGTAILADMIN_BASE_URL = f"https://{BASE_HOSTNAME}" diff --git a/website/urls.py b/website/urls.py index cfc8a51..569566a 100644 --- a/website/urls.py +++ b/website/urls.py @@ -1,9 +1,8 @@ from django.conf import settings -from django.urls import include, path from django.contrib import admin - -from wagtail.admin import urls as wagtailadmin_urls +from django.urls import include, path from wagtail import urls as wagtail_urls +from wagtail.admin import urls as wagtailadmin_urls from wagtail.documents import urls as wagtaildocs_urls from website.search import views as search_views